diff --git a/src/apu/dmc.rs b/src/apu/dmc.rs index 9249d0c..c3da777 100644 --- a/src/apu/dmc.rs +++ b/src/apu/dmc.rs @@ -7,20 +7,24 @@ pub struct DMC { pub enabled: bool, pub interrupt: bool, pub loop_flag: bool, + pub cpu_stall: bool, pub rate_index: usize, pub length_counter: usize, pub cpu_cycles_left: u16, // Memory reader sample_byte: u8, // passed in every APU clock cycle, need to think of a better way to read CPU from APU - sample_buffer: Option, // buffer that the output unit draws into its shift register + sample_buffer: Option, // buffer that the output unit draws into its shift register, wrapped in Option to denote 'emptiness' sample_address: usize, // start of sample in memory sample_length: usize, pub current_address: usize, // address of the next byte of the sample to play pub bytes_remaining: usize, // bytes left in the sample // Output unit - + shift_register: u8, + bits_remaining: usize, + output_level: u8, + silence: bool, } impl DMC { @@ -30,6 +34,7 @@ impl DMC { enabled: false, interrupt: false, loop_flag: false, + cpu_stall: false, rate_index: 0, length_counter: 0, cpu_cycles_left: 0, @@ -39,12 +44,17 @@ impl DMC { sample_length: 0, current_address: 0, bytes_remaining: 0, + shift_register: 0, + bits_remaining: 0, + output_level: 0, + silence: false, } } pub fn clock(&mut self, sample_byte: u8) { // self.sample_byte = sample_byte; self.clock_memory_reader(sample_byte); + self.clock_output_unit(); } fn clock_memory_reader(&mut self, sample_byte: u8) { @@ -63,14 +73,7 @@ impl DMC { if self.sample_buffer.is_none() && self.bytes_remaining != 0 { // The CPU is stalled for up to 4 CPU cycles to allow the longest possible write (the return address and write after an IRQ) to finish. // If OAM DMA is in progress, it is paused for two cycles. The sample fetch always occurs on an even CPU cycle due to its alignment with the APU. - // Specific delay cases: - // 4 cycles if it falls on a CPU read cycle. - // 3 cycles if it falls on a single CPU write cycle (or the second write of a double CPU write). - // 4 cycles if it falls on the first write of a double CPU write cycle.[4] - // 2 cycles if it occurs during an OAM DMA, or on the $4014 write cycle that triggers the OAM DMA. - // 1 cycle if it occurs on the second-last OAM DMA cycle. - // 3 cycles if it occurs on the last OAM DMA cycle. - + self.cpu_stall = true; // The sample buffer is filled with the next sample byte read from the current address, subject to whatever mapping hardware is present. self.sample_buffer = Some(sample_byte); // The address is incremented; if it exceeds $FFFF, it is wrapped around to $8000. @@ -81,10 +84,50 @@ impl DMC { } // The bytes remaining counter is decremented; if it becomes zero and the loop flag is set, the sample is restarted (see above); otherwise, if the bytes remaining counter becomes zero and the IRQ enabled flag is set, the interrupt flag is set. self.bytes_remaining -= 1; + } // At any time, if the interrupt flag is set, the CPU's IRQ line is continuously asserted until the interrupt flag is cleared. // The processor will continue on from where it was stalled. } - + + fn clock_output_unit(&mut self) { + // When the timer outputs a clock, the following actions occur in order: + // If the silence flag is clear, the output level changes based on bit 0 of the shift register. + // If the bit is 1, add 2; otherwise, subtract 2. But if adding or subtracting 2 would cause the output level to leave the 0-127 range, + // leave the output level unchanged. This means subtract 2 only if the current level is at least 2, or add 2 only if the current level is at most 125. + // The right shift register is clocked. + // As stated above, the bits-remaining counter is decremented. If it becomes zero, a new output cycle is started. + if self.cpu_cycles_left == 0 { + self.cpu_cycles_left = SAMPLE_RATES[self.rate_index]; + if !self.silence { + match self.shift_register & 1 { + 0 => if self.output_level >= 2 { self.output_level -= 2}, + 1 => if self.output_level <= 125 { self.output_level += 2 }, + } + } + self.shift_register >>= 1; + self.bits_remaining -= 1; + // When an output cycle ends, a new cycle is started as follows: + // The bits-remaining counter is loaded with 8. + // If the sample buffer is empty, then the silence flag is set; otherwise, the silence flag is cleared and the sample buffer is emptied into the shift register. + if self.bits_remaining == 0 { + self.bits_remaining = 8; + match self.sample_buffer { + Some(s) => { + self.silence = false; + self.shift_register = s; + self.sample_buffer = None; + }, + None => self.silence = true, + } + } + } + self.cpu_cycles_left -= 2; // APU runs every other CPU cycle + if self.dmc.cpu_cycles_left == 0 { + self.dmc.cpu_cycles_left = dmc::SAMPLE_RATES[self.dmc.rate_index]; + } + } + + pub fn write_control(&mut self, value: u8) { // $4010 IL--.RRRR Flags and Rate (write) diff --git a/src/apu/mod.rs b/src/apu/mod.rs index ece99ae..b10db1c 100644 --- a/src/apu/mod.rs +++ b/src/apu/mod.rs @@ -80,15 +80,6 @@ impl Apu { self.cycle = 0; } - // Clock sampler if it's been the correct number of CPU cycles - if self.dmc.cpu_cycles_left == 0 { - self.dmc.clock(sample_byte); - } - self.dmc.cpu_cycles_left -= 2; // APU runs every other CPU cycle - if self.dmc.cpu_cycles_left == 0 { - self.dmc.cpu_cycles_left = dmc::SAMPLE_RATES[self.dmc.rate_index]; - } - // Send all samples to buffer, let the SDL2 audio callback take what it needs self.mix() } diff --git a/src/cpu/mod.rs b/src/cpu/mod.rs index 771be3a..f7c60c1 100644 --- a/src/cpu/mod.rs +++ b/src/cpu/mod.rs @@ -139,28 +139,27 @@ impl Cpu { pub fn step(&mut self) -> u64 { + // The CPU is stalled for up to 4 CPU cycles to allow the longest possible write (the return address and write after an IRQ) to finish. + // If OAM DMA is in progress, it is paused for two cycles. The sample fetch always occurs on an even CPU cycle due to its alignment with the APU. + // Specific delay cases: + // 4 cycles if it falls on a CPU read cycle. + // 3 cycles if it falls on a single CPU write cycle (or the second write of a double CPU write). + // 4 cycles if it falls on the first write of a double CPU write cycle.[4] + // 2 cycles if it occurs during an OAM DMA, or on the $4014 write cycle that triggers the OAM DMA. + // 1 cycle if it occurs on the second-last OAM DMA cycle. + // 3 cycles if it occurs on the last OAM DMA cycle. + if self.apu.dmc.cpu_stall { + self.delay = 3; + self.apu.dmc.cpu_stall = false; + } + // skip cycles from OAM DMA if necessary if self.delay > 0 { self.delay -= 1; return 1; } - // handle interrupts from ppu - if self.ppu.trigger_nmi { - self.nmi(); - } - self.ppu.trigger_nmi = false; - // and apu - if self.apu.trigger_irq && (self.p & INTERRUPT_DISABLE_FLAG == 0) { - self.irq(); - } - self.apu.trigger_irq = false; - // and mapper MMC3 - if self.mapper.borrow_mut().check_irq() && (self.p & INTERRUPT_DISABLE_FLAG == 0) { - self.irq(); - } - // TODO: should checks for APU and MMC3 IRQs be combined and acknowledged together? - + self.handle_interrupts(); // back up clock so we know how many cycles we complete let clock = self.clock; @@ -263,6 +262,30 @@ impl Cpu { } } + fn handle_interrupts(&mut self) { + // handle interrupts from ppu + if self.ppu.trigger_nmi { + self.nmi(); + } + self.ppu.trigger_nmi = false; + // and apu + if self.apu.trigger_irq && (self.p & INTERRUPT_DISABLE_FLAG == 0) { + self.irq(); + } + self.apu.trigger_irq = false; + // and mapper MMC3 + if self.mapper.borrow_mut().check_irq() && (self.p & INTERRUPT_DISABLE_FLAG == 0) { + self.irq(); + } + // TODO: should checks for APU and MMC3 IRQs be combined and acknowledged together? + + // At any time, if the interrupt flag is set, the CPU's IRQ line is continuously asserted until the interrupt flag is cleared. + // The processor will continue on from where it was stalled. + if self.apu.dmc.interrupt { + self.irq(); + } + } + fn _debug(&mut self, num_bytes: usize, opcode: usize) { let pc = self.pc; let operands = match num_bytes {