use crate::console::TimingMode;
use crate::trace_apu;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DmcState {
pub timer: u16,
pub timer_period: u16,
pub output_level: u8,
pub sample_address: u16,
pub sample_length: u16,
pub current_address: u16,
pub bytes_remaining: u16,
pub sample_buffer: Option<u8>,
pub shift_register: u8,
pub bits_remaining: u8,
pub silence_flag: bool,
pub irq_enabled: bool,
pub irq_flag: bool,
pub loop_flag: bool,
pub dma_pending: bool,
#[serde(default)]
pub dma_pending_deferred: bool,
pub transfer_start_delay: u8,
#[serde(default)]
pub disable_delay: u8,
}
const DMC_RATE_TABLE_NTSC: [u16; 16] = [
428, 380, 340, 320, 286, 254, 226, 214, 190, 160, 142, 128, 106, 84, 72, 54,
];
const DMC_RATE_TABLE_PAL: [u16; 16] = [
398, 354, 316, 298, 276, 236, 210, 198, 176, 148, 132, 118, 98, 78, 66, 50,
];
pub struct Dmc {
tv_system: TimingMode,
timer: u16,
timer_period: u16,
irq_enabled: bool,
loop_flag: bool,
output_level: u8,
sample_buffer: Option<u8>,
shift_register: u8,
bits_remaining: u8,
silence_flag: bool,
sample_address: u16,
sample_length: u16,
current_address: u16,
bytes_remaining: u16,
dma_pending: bool,
dma_pending_deferred: bool,
interrupt_flag: bool,
#[cfg(test)]
irq_trigger_count: u32,
transfer_start_delay: u8,
disable_delay: u8,
}
impl Default for Dmc {
fn default() -> Self {
Self::new()
}
}
impl Dmc {
pub fn new() -> Self {
Self::new_with_tv_system(TimingMode::Ntsc)
}
pub fn new_with_tv_system(tv_system: TimingMode) -> Self {
let timer_period = match tv_system {
TimingMode::Ntsc => DMC_RATE_TABLE_NTSC[0],
TimingMode::Pal => DMC_RATE_TABLE_PAL[0],
TimingMode::MultiRegion | TimingMode::Dendy | TimingMode::Unknown(_) => {
DMC_RATE_TABLE_NTSC[0]
}
};
Dmc {
tv_system,
timer: timer_period.saturating_sub(1),
timer_period,
irq_enabled: false,
loop_flag: false,
output_level: 0,
sample_buffer: None,
shift_register: 0,
bits_remaining: 8,
silence_flag: true,
sample_address: 0,
sample_length: 0,
current_address: 0,
bytes_remaining: 0,
dma_pending: false,
dma_pending_deferred: false,
interrupt_flag: false,
#[cfg(test)]
irq_trigger_count: 0,
transfer_start_delay: 0,
disable_delay: 0,
}
}
pub fn reset(&mut self) {
trace_apu!(2; "dmc reset");
let tv_system = self.tv_system;
*self = Self::new_with_tv_system(tv_system);
}
pub fn reinit_timer_after_reset(&mut self) {
self.timer = self.timer_period.saturating_sub(1);
}
#[cfg(test)]
pub fn dma_pending(&self) -> bool {
self.dma_pending
}
pub fn cpu_dma_pending(&self) -> bool {
self.dma_pending && !self.dma_pending_deferred
}
#[cfg(test)]
pub fn debug_timer(&self) -> u16 {
self.timer
}
#[cfg(test)]
pub fn debug_set_dma_pending(&mut self, value: bool) {
self.dma_pending = value;
self.dma_pending_deferred = false;
}
#[cfg(test)]
pub fn debug_set_transfer_start_delay(&mut self, value: u8) {
self.transfer_start_delay = value;
}
#[cfg(test)]
pub fn debug_transfer_start_delay(&self) -> u8 {
self.transfer_start_delay
}
pub fn dma_address(&self) -> Option<u16> {
(self.dma_pending && self.bytes_remaining > 0).then_some(self.current_address)
}
pub fn complete_dma_read(&mut self, value: u8) {
if !self.dma_pending {
return;
}
self.dma_pending = false;
self.dma_pending_deferred = false;
self.sample_buffer = Some(value);
trace_apu!(4; "dmc sample_buffer set value=0x{:02X}", value);
trace_apu!(3; "dmc complete_dma_read value=0x{:02X}", value);
self.advance_reader_after_fetch();
}
pub fn output(&self) -> u8 {
self.output_level
}
pub fn write_flags_and_rate(&mut self, value: u8) {
self.irq_enabled = (value >> 7) & 1 == 1;
self.loop_flag = (value >> 6) & 1 == 1;
let rate_index = (value & 0x0F) as usize;
self.timer_period = match self.tv_system {
TimingMode::Ntsc => DMC_RATE_TABLE_NTSC[rate_index],
TimingMode::Pal => DMC_RATE_TABLE_PAL[rate_index],
TimingMode::MultiRegion | TimingMode::Dendy | TimingMode::Unknown(_) => {
DMC_RATE_TABLE_NTSC[rate_index]
}
};
trace_apu!(2; "dmc write_flags_and_rate value=0x{:02X} irq_enabled={} loop={} rate_index={} period={}", value, self.irq_enabled, self.loop_flag, rate_index, self.timer_period);
if !self.irq_enabled {
self.interrupt_flag = false;
}
}
pub fn write_direct_load(&mut self, value: u8) {
self.output_level = value & 0x7F;
trace_apu!(2; "dmc write_direct_load value=0x{:02X} output_level={}", value, self.output_level);
}
pub fn write_sample_address(&mut self, value: u8) {
self.sample_address = 0xC000 + (value as u16 * 64);
trace_apu!(2; "dmc write_sample_address value=0x{:02X} address=0x{:04X}", value, self.sample_address);
}
pub fn write_sample_length(&mut self, value: u8) {
self.sample_length = (value as u16 * 16) + 1;
trace_apu!(2; "dmc write_sample_length value=0x{:02X} length={}", value, self.sample_length);
}
pub fn clock_timer(&mut self) {
if self.transfer_start_delay == 0 {
self.fill_sample_buffer_if_needed(false);
}
if self.timer == 0 {
self.timer = self.timer_period.saturating_sub(1);
self.clock_output_unit();
if self.transfer_start_delay == 0 {
self.fill_sample_buffer_if_needed(true);
}
trace_apu!(4; "dmc clock_timer reload period={} output_level={}", self.timer_period, self.output_level);
} else {
self.timer -= 1;
}
}
fn fill_sample_buffer_if_needed(&mut self, defer_dma_visibility: bool) {
if self.sample_buffer.is_some() {
return;
}
if self.bytes_remaining == 0 {
return;
}
if !self.dma_pending {
self.dma_pending = true;
self.dma_pending_deferred = defer_dma_visibility;
trace_apu!(4; "dmc dma_pending address=0x{:04X} bytes_remaining={}", self.current_address, self.bytes_remaining);
}
}
fn advance_reader_after_fetch(&mut self) {
if self.bytes_remaining == 0 {
return;
}
self.current_address = self.current_address.wrapping_add(1);
if self.current_address == 0x0000 {
self.current_address = 0x8000;
}
self.bytes_remaining -= 1;
trace_apu!(4; "dmc advance_reader address=0x{:04X} bytes_remaining={}", self.current_address, self.bytes_remaining);
if self.bytes_remaining == 0 {
if self.loop_flag {
self.restart_sample();
} else if self.irq_enabled {
self.interrupt_flag = true;
#[cfg(test)]
{
self.irq_trigger_count += 1;
}
trace_apu!(3; "dmc irq_flag set (sample complete)");
}
}
}
fn clock_output_unit(&mut self) {
if !self.silence_flag {
let bit0 = self.shift_register & 1;
trace_apu!(4; "dmc clock_output_unit bit0={}", bit0);
if bit0 == 1 {
if self.output_level <= 125 {
self.output_level += 2;
}
} else {
if self.output_level >= 2 {
self.output_level -= 2;
}
}
}
self.shift_register >>= 1;
if self.bits_remaining > 0 {
self.bits_remaining -= 1;
}
trace_apu!(4; "dmc clock_output_unit shift_register=0x{:02X}, bits_remaining={}", self.shift_register, self.bits_remaining);
if self.bits_remaining == 0 {
self.start_output_cycle();
}
}
fn start_output_cycle(&mut self) {
self.bits_remaining = 8;
if let Some(sample) = self.sample_buffer {
self.silence_flag = false;
self.shift_register = sample;
self.sample_buffer = None;
trace_apu!(4; "dmc start_output_cycle shift=0x{:02X}", self.shift_register);
trace_apu!(4; "dmc sample_buffer cleared (loaded into shift)");
} else {
self.silence_flag = true;
trace_apu!(4; "dmc start_output_cycle silence");
}
}
fn restart_sample(&mut self) {
self.current_address = self.sample_address;
self.bytes_remaining = self.sample_length;
trace_apu!(3; "dmc restart_sample address=0x{:04X} length={}", self.current_address, self.bytes_remaining);
}
fn delay_for_cpu_cycle(cpu_cycle: u64, even_cycle_delay: u8, odd_cycle_delay: u8) -> u8 {
if cpu_cycle.is_multiple_of(2) {
even_cycle_delay
} else {
odd_cycle_delay
}
}
fn process_disable_delay(&mut self) {
if self.disable_delay == 0 {
return;
}
self.disable_delay -= 1;
if self.disable_delay == 0 {
self.bytes_remaining = 0;
self.dma_pending = false;
self.dma_pending_deferred = false;
trace_apu!(4; "dmc disable_delay expired");
}
}
fn process_transfer_start_delay(&mut self) {
if self.transfer_start_delay == 0 {
return;
}
self.transfer_start_delay -= 1;
if self.transfer_start_delay == 0 {
self.fill_sample_buffer_if_needed(false);
trace_apu!(4; "dmc transfer_start_delay expired");
}
}
pub fn set_enabled(&mut self, enabled: bool, cpu_cycle: u64) {
trace_apu!(2; "dmc set_enabled {} cpu_cycle={}", enabled, cpu_cycle);
if enabled {
self.disable_delay = 0;
if self.bytes_remaining == 0 {
self.restart_sample();
self.transfer_start_delay = Self::delay_for_cpu_cycle(cpu_cycle, 1, 2);
trace_apu!(4; "dmc transfer_start_delay {}", self.transfer_start_delay);
}
} else {
if self.disable_delay == 0 {
self.disable_delay = Self::delay_for_cpu_cycle(cpu_cycle, 2, 3);
}
if self.sample_buffer.is_some() {
trace_apu!(4; "dmc sample_buffer retained (disable)");
}
}
}
pub fn process_clock(&mut self) {
self.dma_pending_deferred = false;
self.process_disable_delay();
self.process_transfer_start_delay();
}
#[cfg(test)]
fn finish_byte(&mut self) {
if self.bytes_remaining > 0 {
self.bytes_remaining -= 1;
if self.bytes_remaining == 0 {
if self.loop_flag {
self.restart_sample();
} else if self.irq_enabled {
self.interrupt_flag = true;
#[cfg(test)]
{
self.irq_trigger_count += 1;
}
}
}
}
}
pub fn get_irq_flag(&self) -> bool {
self.interrupt_flag
}
#[cfg(test)]
#[allow(dead_code)]
pub fn debug_irq_trigger_count(&self) -> u32 {
self.irq_trigger_count
}
pub fn clear_irq_flag(&mut self) {
self.interrupt_flag = false;
trace_apu!(3; "dmc clear_irq_flag");
}
pub fn has_bytes_remaining(&self) -> bool {
self.bytes_remaining > 0
}
#[cfg(test)]
#[allow(dead_code)]
pub fn get_bytes_remaining(&self) -> u16 {
self.bytes_remaining
}
pub fn capture_state(&self) -> DmcState {
DmcState {
timer: self.timer,
timer_period: self.timer_period,
output_level: self.output_level,
sample_address: self.sample_address,
sample_length: self.sample_length,
current_address: self.current_address,
bytes_remaining: self.bytes_remaining,
sample_buffer: self.sample_buffer,
shift_register: self.shift_register,
bits_remaining: self.bits_remaining,
silence_flag: self.silence_flag,
irq_enabled: self.irq_enabled,
irq_flag: self.interrupt_flag,
loop_flag: self.loop_flag,
dma_pending: self.dma_pending,
dma_pending_deferred: self.dma_pending_deferred,
transfer_start_delay: self.transfer_start_delay,
disable_delay: self.disable_delay,
}
}
pub fn restore_state(&mut self, state: &DmcState) {
self.timer = state.timer;
self.timer_period = state.timer_period;
self.output_level = state.output_level;
self.sample_address = state.sample_address;
self.sample_length = state.sample_length;
self.current_address = state.current_address;
self.bytes_remaining = state.bytes_remaining;
self.sample_buffer = state.sample_buffer;
self.shift_register = state.shift_register;
self.bits_remaining = state.bits_remaining;
self.silence_flag = state.silence_flag;
self.irq_enabled = state.irq_enabled;
self.interrupt_flag = state.irq_flag;
self.loop_flag = state.loop_flag;
self.dma_pending = state.dma_pending;
self.dma_pending_deferred = state.dma_pending_deferred;
self.transfer_start_delay = state.transfer_start_delay;
self.disable_delay = state.disable_delay;
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::console::TimingMode;
#[test]
fn test_dmc_new() {
let dmc = Dmc::new();
assert_eq!(dmc.output(), 0);
assert_eq!(dmc.timer_period, 428); assert_eq!(dmc.bits_remaining, 8);
assert!(dmc.silence_flag);
}
#[test]
fn test_dmc_pal_rate_table() {
let mut dmc = Dmc::new_with_tv_system(TimingMode::Pal);
assert_eq!(dmc.timer_period, 398);
dmc.write_flags_and_rate(0x0F);
assert_eq!(dmc.timer_period, 50);
}
#[test]
fn test_write_flags_and_rate() {
let mut dmc = Dmc::new();
dmc.write_flags_and_rate(0b1100_0101);
assert!(dmc.irq_enabled);
assert!(dmc.loop_flag);
assert_eq!(dmc.timer_period, 254); }
#[test]
fn test_write_direct_load() {
let mut dmc = Dmc::new();
dmc.write_direct_load(0b0111_1111);
assert_eq!(dmc.output(), 127);
dmc.write_direct_load(0b0100_0000); assert_eq!(dmc.output(), 64);
}
#[test]
fn test_write_sample_address() {
let mut dmc = Dmc::new();
dmc.write_sample_address(0x00); assert_eq!(dmc.sample_address, 0xC000);
dmc.write_sample_address(0x01); assert_eq!(dmc.sample_address, 0xC040);
dmc.write_sample_address(0xFF); assert_eq!(dmc.sample_address, 0xFFC0);
}
#[test]
fn test_write_sample_length() {
let mut dmc = Dmc::new();
dmc.write_sample_length(0x00); assert_eq!(dmc.sample_length, 1);
dmc.write_sample_length(0x01); assert_eq!(dmc.sample_length, 17);
dmc.write_sample_length(0xFF); assert_eq!(dmc.sample_length, 4081);
}
#[test]
fn test_timer_clocking() {
let mut dmc = Dmc::new();
dmc.write_flags_and_rate(0b0000_1111);
assert_eq!(dmc.timer, 427);
assert_eq!(dmc.timer_period, 54);
dmc.clock_timer();
assert_eq!(dmc.timer, 426);
dmc.clock_timer();
assert_eq!(dmc.timer, 425);
}
#[test]
fn test_output_level_increment() {
let mut dmc = Dmc::new();
dmc.output_level = 50;
dmc.shift_register = 0b0000_0001; dmc.silence_flag = false;
dmc.bits_remaining = 1;
dmc.clock_output_unit();
assert_eq!(dmc.output_level, 52);
}
#[test]
fn test_output_level_decrement() {
let mut dmc = Dmc::new();
dmc.output_level = 50;
dmc.shift_register = 0b0000_0000; dmc.silence_flag = false;
dmc.bits_remaining = 1;
dmc.clock_output_unit();
assert_eq!(dmc.output_level, 48);
}
#[test]
fn test_output_level_clamping() {
let mut dmc = Dmc::new();
dmc.silence_flag = false;
dmc.bits_remaining = 5;
dmc.output_level = 126;
dmc.shift_register = 0b0000_0001; dmc.clock_output_unit();
assert_eq!(dmc.output_level, 126);
dmc.output_level = 125;
dmc.shift_register = 0b0000_0001;
dmc.bits_remaining = 5;
dmc.clock_output_unit();
assert_eq!(dmc.output_level, 127);
dmc.output_level = 1;
dmc.shift_register = 0b0000_0000; dmc.bits_remaining = 5;
dmc.clock_output_unit();
assert_eq!(dmc.output_level, 1);
dmc.output_level = 2;
dmc.shift_register = 0b0000_0000;
dmc.bits_remaining = 5;
dmc.clock_output_unit();
assert_eq!(dmc.output_level, 0); }
#[test]
fn test_output_cycle_with_sample_buffer() {
let mut dmc = Dmc::new();
dmc.sample_buffer = Some(0b1010_1010);
dmc.silence_flag = true;
dmc.bits_remaining = 0;
dmc.start_output_cycle();
assert_eq!(dmc.shift_register, 0b1010_1010);
assert!(!dmc.silence_flag);
assert_eq!(dmc.bits_remaining, 8);
assert!(dmc.sample_buffer.is_none());
}
#[test]
fn test_output_cycle_without_sample_buffer() {
let mut dmc = Dmc::new();
dmc.sample_buffer = None;
dmc.silence_flag = false;
dmc.bits_remaining = 0;
dmc.start_output_cycle();
assert!(dmc.silence_flag);
assert_eq!(dmc.bits_remaining, 8);
}
#[test]
fn test_clock_timer_raises_dma_pending_on_same_cycle_sample_buffer_is_consumed() {
let mut dmc = Dmc::new();
dmc.write_sample_address(0x00);
dmc.current_address = 0xC000;
dmc.bytes_remaining = 2;
dmc.dma_pending = true;
dmc.complete_dma_read(0xAA);
dmc.bits_remaining = 1;
dmc.timer = 0;
dmc.transfer_start_delay = 0;
dmc.dma_pending = false;
assert_eq!(dmc.sample_buffer, Some(0xAA));
assert!(!dmc.dma_pending());
dmc.clock_timer();
assert!(dmc.sample_buffer.is_none());
assert!(
dmc.dma_pending(),
"expected dma_pending to be raised on the same clock_timer call that consumed the sample buffer"
);
assert!(!dmc.cpu_dma_pending());
}
#[test]
fn test_deferred_rollover_dma_becomes_cpu_visible_next_cycle() {
let mut dmc = Dmc::new();
dmc.write_sample_address(0x00);
dmc.current_address = 0xC000;
dmc.bytes_remaining = 2;
dmc.dma_pending = true;
dmc.complete_dma_read(0xAA);
dmc.bits_remaining = 1;
dmc.timer = 0;
dmc.transfer_start_delay = 0;
dmc.dma_pending = false;
dmc.clock_timer();
assert!(dmc.dma_pending());
assert!(!dmc.cpu_dma_pending());
dmc.process_clock();
assert!(dmc.cpu_dma_pending());
}
#[test]
fn test_sample_buffer_retained_when_disabled() {
let mut dmc = Dmc::new();
dmc.sample_buffer = Some(0x55);
dmc.set_enabled(false, 0);
assert_eq!(dmc.sample_buffer, Some(0x55));
}
#[test]
fn test_disable_channel_does_not_force_silence_flag() {
let mut dmc = Dmc::new();
dmc.silence_flag = false;
dmc.shift_register = 0xAA;
dmc.bits_remaining = 4;
dmc.set_enabled(false, 0);
assert!(!dmc.silence_flag);
assert_eq!(dmc.shift_register, 0xAA);
assert_eq!(dmc.bits_remaining, 4);
}
#[test]
fn test_cold_start_timer_starts_at_full_period() {
let dmc = Dmc::new();
let expected_timer = DMC_RATE_TABLE_NTSC[0] - 1; assert_eq!(
dmc.debug_timer(),
expected_timer,
"timer should start at full period ({expected_timer}), not 0"
);
}
#[test]
fn test_reset_timer_starts_at_full_period() {
let mut dmc = Dmc::new();
for _ in 0..10 {
dmc.clock_timer();
}
assert_ne!(dmc.debug_timer(), DMC_RATE_TABLE_NTSC[0] - 1);
dmc.reset();
let expected_timer = DMC_RATE_TABLE_NTSC[0] - 1;
assert_eq!(
dmc.debug_timer(),
expected_timer,
"after reset, timer should restart at full period ({expected_timer})"
);
}
#[test]
fn test_first_dmc_tick_occurs_after_full_period_cycles() {
let mut dmc = Dmc::new();
let period = DMC_RATE_TABLE_NTSC[0];
dmc.silence_flag = false;
dmc.shift_register = 0xFF; dmc.bits_remaining = 8;
dmc.output_level = 0;
for i in 0..(period - 1) {
dmc.clock_timer();
assert_eq!(
dmc.output_level, 0,
"output level should not change before full period (cycle {i})"
);
}
dmc.clock_timer();
assert_eq!(
dmc.output_level, 2,
"output level should change on the {period}th cycle (full period)"
);
}
}
#[cfg(test)]
mod sample_tests {
use std::cell::RefCell;
use std::rc::Rc;
use super::*;
use crate::cartridge::Cartridge;
use crate::console::{Config, Nes};
fn make_ines_nrom_32k(prg_rom: &[u8]) -> Vec<u8> {
assert_eq!(prg_rom.len(), 2 * 16 * 1024);
let mut rom = Vec::with_capacity(16 + prg_rom.len());
rom.extend_from_slice(b"NES\x1A");
rom.push(2); rom.push(0); rom.push(0); rom.push(0); rom.push(0); rom.push(0);
rom.push(0);
rom.extend_from_slice(&[0u8; 5]);
rom.extend_from_slice(prg_rom);
rom
}
#[test]
fn test_restart_sample() {
let mut dmc = Dmc::new();
dmc.write_sample_address(0x10); dmc.write_sample_length(0x0F);
dmc.restart_sample();
assert_eq!(dmc.current_address, 0xC400);
assert_eq!(dmc.bytes_remaining, 241);
}
#[test]
fn test_enable_channel_restarts_sample() {
let mut dmc = Dmc::new();
dmc.write_sample_address(0x20); dmc.write_sample_length(0x01); dmc.bytes_remaining = 0;
dmc.set_enabled(true, 0);
assert_eq!(dmc.current_address, 0xC800);
assert_eq!(dmc.bytes_remaining, 17);
}
#[test]
fn test_disable_channel_waits_two_cycles_before_clearing_on_even_cpu_cycle() {
let mut dmc = Dmc::new();
dmc.bytes_remaining = 100;
dmc.set_enabled(false, 0);
assert_eq!(dmc.bytes_remaining, 100);
dmc.process_clock();
assert_eq!(dmc.bytes_remaining, 100);
dmc.process_clock();
assert_eq!(dmc.bytes_remaining, 0);
}
#[test]
fn test_disable_channel_waits_three_cycles_before_clearing_on_odd_cpu_cycle() {
let mut dmc = Dmc::new();
dmc.bytes_remaining = 100;
dmc.set_enabled(false, 1);
assert_eq!(dmc.bytes_remaining, 100);
dmc.process_clock();
assert_eq!(dmc.bytes_remaining, 100);
dmc.process_clock();
assert_eq!(dmc.bytes_remaining, 100);
dmc.process_clock();
assert_eq!(dmc.bytes_remaining, 0);
}
#[test]
fn test_irq_flag_set_when_sample_ends() {
let mut dmc = Dmc::new();
dmc.write_flags_and_rate(0b1000_0000); dmc.bytes_remaining = 1;
dmc.loop_flag = false;
dmc.finish_byte();
assert!(dmc.interrupt_flag);
assert_eq!(dmc.bytes_remaining, 0);
}
#[test]
fn test_loop_restarts_sample() {
let mut dmc = Dmc::new();
dmc.write_flags_and_rate(0b0100_0000); dmc.write_sample_address(0x30); dmc.write_sample_length(0x02); dmc.current_address = 0xCC20;
dmc.bytes_remaining = 1;
dmc.finish_byte();
assert_eq!(dmc.current_address, 0xCC00);
assert_eq!(dmc.bytes_remaining, 33);
assert!(!dmc.interrupt_flag); }
#[test]
fn test_irq_flag_cleared_when_disabled() {
let mut dmc = Dmc::new();
dmc.interrupt_flag = true;
dmc.write_flags_and_rate(0b0000_0000);
assert!(!dmc.interrupt_flag);
}
#[test]
fn test_get_irq_flag() {
let mut dmc = Dmc::new();
assert!(!dmc.get_irq_flag());
dmc.interrupt_flag = true;
assert!(dmc.get_irq_flag());
}
#[test]
fn test_get_bytes_remaining_status() {
let mut dmc = Dmc::new();
dmc.bytes_remaining = 10;
assert!(dmc.has_bytes_remaining());
dmc.bytes_remaining = 0;
assert!(!dmc.has_bytes_remaining());
}
#[test]
fn test_dmc_reads_sample_byte_from_cpu_memory() {
let mut prg = vec![0xEAu8; 2 * 16 * 1024]; prg[0x0000] = 0xEA;
prg[0x0001] = 0xEA;
prg[0x0002] = 0xEA;
prg[0x0003] = 0x4C;
prg[0x0004] = 0x00;
prg[0x0005] = 0x80;
prg[0x4000] = 0xFF;
prg[0x7FFA] = 0x00;
prg[0x7FFB] = 0x80;
prg[0x7FFC] = 0x00;
prg[0x7FFD] = 0x80;
prg[0x7FFE] = 0x00;
prg[0x7FFF] = 0x80;
let rom = make_ines_nrom_32k(&prg);
let cartridge = Cartridge::load_from_file(
&rom,
"apu-dmc-test-rom.nes",
Rc::new(RefCell::new(crate::app_context::AppContext::new())),
)
.expect("test ROM should parse");
let mut nes = Nes::new(Rc::new(RefCell::new(
crate::app_context::AppContext::new_with_config(Config::default()),
)));
nes.insert_cartridge(cartridge);
nes.reset(false);
{
let mut apu = nes.apu().borrow_mut();
apu.dmc_mut().write_flags_and_rate(0x0F);
apu.dmc_mut().write_sample_address(0x00);
apu.dmc_mut().write_sample_length(0x00);
apu.write_enable(0x10);
}
for _ in 0..5_000 {
nes.run_cpu_tick();
}
let output = nes.apu().borrow().dmc().output();
assert!(
output > 0,
"expected DMC output to be > 0 after reading sample; got {output}"
);
}
#[test]
fn reset_restores_dmc_to_initial_state() {
let mut dmc = Dmc::new();
dmc.write_flags_and_rate(0b1100_1111); dmc.write_direct_load(0x40); dmc.write_sample_address(0x40); dmc.write_sample_length(0x10); dmc.interrupt_flag = true;
dmc.sample_buffer = Some(0xAB);
dmc.shift_register = 0xFF;
dmc.bits_remaining = 3;
dmc.silence_flag = false;
dmc.dma_pending = true;
dmc.bytes_remaining = 100;
dmc.current_address = 0xD000;
assert!(dmc.get_irq_flag());
assert_eq!(dmc.output(), 64);
assert!(dmc.has_bytes_remaining());
assert!(dmc.dma_pending());
dmc.reset();
assert!(!dmc.get_irq_flag());
assert_eq!(dmc.output(), 0);
assert!(!dmc.has_bytes_remaining());
assert!(!dmc.dma_pending());
assert_eq!(dmc.bits_remaining, 8);
}
}