diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 68aa5ab..d5e0c3f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,7 +26,7 @@ jobs: run: cargo fmt --all -- --check - name: Clippy check - run: cargo clippy --all-targets --all-features + run: cargo clippy --all-targets --no-default-features --features "log trace" tests: runs-on: ubuntu-latest @@ -39,4 +39,4 @@ jobs: toolchain: nightly - name: Run tests - run: cargo test --package aliusnes + run: cargo test --package aliusnes --no-default-features diff --git a/.gitignore b/.gitignore index 2ebc5ea..426df58 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ /target -/Cargo.lock \ No newline at end of file +/Cargo.lock +/aliusnes/src/apu/spc700/ipl_boot.rom diff --git a/aliusnes/Cargo.toml b/aliusnes/Cargo.toml index 95ee1da..550fa68 100644 --- a/aliusnes/Cargo.toml +++ b/aliusnes/Cargo.toml @@ -5,6 +5,8 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [features] +default = ["apu"] +apu = [] log = ["simplelog"] trace = ["log"] diff --git a/aliusnes/src/apu/dsp/mod.rs b/aliusnes/src/apu/dsp/mod.rs new file mode 100644 index 0000000..20e3e1b --- /dev/null +++ b/aliusnes/src/apu/dsp/mod.rs @@ -0,0 +1,146 @@ +use crate::apu::dsp::voice::Voice; + +mod voice; + +bitfield! { + #[derive(Clone, Copy)] + struct Flags(u8) { + pub noise_frequency: u8 @ 0..=4, + pub disable_echo_write: bool @ 5, + pub mute_all: bool @ 6, + pub soft_reset: bool @ 7, + } +} + +pub(crate) struct Dsp { + dsp_addr: usize, + voices: [Voice; 8], + left_main_channel_volume: i8, + right_main_channel_volume: i8, + left_echo_volume: i8, + right_echo_volume: i8, + key_on: u8, + key_off: u8, + flags: Flags, + end_flag_channel_mask: u8, + echo_feedback: i8, + + unused: u8, + + pitch_modulation_channel_mask: u8, + noise_enabled_channel_mask: u8, + echo_enabled_channel_mask: u8, + + pointer_to_sample_directory: u8, + pointer_to_echo_buffer: u8, + echo_delay: u8, + echo_filter_coefficients: [i8; 8], +} + +impl Dsp { + pub(crate) fn new() -> Dsp { + Dsp { + dsp_addr: 0, + voices: [Voice::new(); 8], + left_main_channel_volume: 0, + right_main_channel_volume: 0, + left_echo_volume: 0, + right_echo_volume: 0, + key_on: 0, + key_off: 0, + flags: Flags(0), + end_flag_channel_mask: 0, + echo_feedback: 0, + unused: 0, + pitch_modulation_channel_mask: 0, + noise_enabled_channel_mask: 0, + echo_enabled_channel_mask: 0, + pointer_to_sample_directory: 0, + pointer_to_echo_buffer: 0, + echo_delay: 0, + echo_filter_coefficients: [0; 8], + } + } + + pub(crate) fn set_dsp_addr(&mut self, value: u8) { + self.dsp_addr = value.into(); + } + + pub(crate) fn read_dsp_addr(&self) -> u8 { + self.dsp_addr as u8 + } + + pub(crate) fn read(&self) -> u8 { + let addr = self.dsp_addr & 0x7F; + let index = addr >> 4; + + match addr & 0xF { + low_nibble @ 0x0..=0x9 => return self.voices[index].read(low_nibble), + 0xC => match index { + 0x0 => return self.left_main_channel_volume as u8, + 0x1 => return self.right_main_channel_volume as u8, + 0x2 => return self.left_echo_volume as u8, + 0x3 => return self.right_echo_volume as u8, + 0x4 => return self.key_on, + 0x5 => return self.key_off, + 0x6 => return self.flags.0, + 0x7 => return self.end_flag_channel_mask, + _ => (), + }, + 0xD => match index { + 0x0 => return self.echo_feedback as u8, + 0x1 => return self.unused, + 0x2 => return self.pitch_modulation_channel_mask, + 0x3 => return self.noise_enabled_channel_mask, + 0x4 => return self.echo_enabled_channel_mask, + 0x5 => return self.pointer_to_sample_directory, + 0x6 => return self.pointer_to_echo_buffer, + 0x7 => return self.echo_delay, + _ => (), + }, + 0xF => return self.echo_filter_coefficients[index] as u8, + _ => (), + } + + println!("Read from invalid DSP register: {:#04X}", self.dsp_addr); + 0 + } + + pub(crate) fn write(&mut self, data: u8) { + if self.dsp_addr >= 0x80 { + return; + } + let addr = self.dsp_addr & 0x7F; + let index = addr >> 4; + + match addr & 0xF { + low_nibble @ 0x0..=0x7 => self.voices[index].write(low_nibble, data), + 0xC => match index { + 0x0 => self.left_main_channel_volume = data as i8, + 0x1 => self.right_main_channel_volume = data as i8, + 0x2 => self.left_echo_volume = data as i8, + 0x3 => self.right_echo_volume = data as i8, + 0x4 => self.key_on = data, + 0x5 => self.key_off = data, + 0x6 => self.flags = Flags(data), + _ => (), + }, + 0xD => match index { + 0x0 => self.echo_feedback = data as i8, + 0x1 => self.unused = data, + 0x2 => self.pitch_modulation_channel_mask = data, + 0x3 => self.noise_enabled_channel_mask = data, + 0x4 => self.echo_enabled_channel_mask = data, + 0x5 => self.pointer_to_sample_directory = data, + 0x6 => self.pointer_to_echo_buffer = data, + 0x7 => self.echo_delay = data, + _ => (), + }, + 0xF => self.echo_filter_coefficients[index] = data as i8, + _ => (), + } + } + + #[allow(dead_code)] + pub(crate) fn output_sample(&mut self, _ram: &[u8]) {} +} diff --git a/aliusnes/src/apu/dsp/voice.rs b/aliusnes/src/apu/dsp/voice.rs new file mode 100644 index 0000000..3fdc8e2 --- /dev/null +++ b/aliusnes/src/apu/dsp/voice.rs @@ -0,0 +1,140 @@ +use std::hint::unreachable_unchecked; + +use crate::utils::int_traits::ManipulateU16; + +bitfield! { + #[derive(Clone, Copy)] + struct Adsr(u16) { + attack_rate: u8 @ 0..=3, + decay_rate: u8 @ 4..=6, + enabled: bool @ 7, + sustain_rate: u8 @ 8..=12, + sustain_level: u8 @ 13..=15, + } +} + +bitfield! { + #[derive(Clone, Copy)] + struct Gain(u8) { + fixed_volume: u8 @ 0..=6, + rate: u8 @ 0..=4, + mode: u8 @ 5..=6, + use_custom: bool @ 7, + } +} + +bitfield! { + struct ControlBlock(u8) { + end: bool @ 0, + loop_sample: bool @ 1, + filter: u8 @ 2..=3, + left_shift: u8 @ 4..=7, + } +} + +const FILTER_TABLE: [[f32; 2]; 4] = [ + [0.0, 0.0], + [15.0 / 16.0, 0.0], + [61.0 / 32.0, 15.0 / 16.0], + [115.0 / 64.0, 13.0 / 16.0], +]; + +#[derive(Clone, Copy)] +pub struct Voice { + left_channel_volume: i8, + right_channel_volume: i8, + sample_pitch: u16, + sample_source_entry: u8, + adsr: Adsr, + gain: Gain, + current_envelope: u8, + current_sample: u8, +} + +impl Voice { + pub(crate) fn new() -> Voice { + Voice { + left_channel_volume: 0, + right_channel_volume: 0, + sample_pitch: 0, + sample_source_entry: 0, + adsr: Adsr(0), + gain: Gain(0), + current_envelope: 0, + current_sample: 0, + } + } + + pub(crate) fn read(&self, low_nibble: usize) -> u8 { + match low_nibble { + 0x0 => self.left_channel_volume as u8, + 0x1 => self.right_channel_volume as u8, + 0x2 => self.sample_pitch.low_byte(), + 0x3 => self.sample_pitch.high_byte(), + 0x4 => self.sample_source_entry, + 0x5 => self.adsr.0.low_byte(), + 0x6 => self.adsr.0.high_byte(), + 0x7 => self.gain.0, + 0x8 => self.current_envelope, + 0x9 => self.current_sample, + _ => unsafe { unreachable_unchecked() }, + } + } + + pub(crate) fn write(&mut self, low_nibble: usize, data: u8) { + match low_nibble { + 0x0 => self.left_channel_volume = data as i8, + 0x1 => self.right_channel_volume = data as i8, + 0x2 => self.sample_pitch.set_low_byte(data), + 0x3 => self.sample_pitch.set_high_byte(data), + 0x4 => self.sample_source_entry = data, + 0x5 => self.adsr.0.set_low_byte(data), + 0x6 => self.adsr.0.set_high_byte(data), + 0x7 => self.gain = Gain(data), + _ => unsafe { unreachable_unchecked() }, + } + } + + #[allow(dead_code)] + fn sample_entry(&self, dir: u16) -> u16 { + dir * 0x100 + self.sample_source_entry as u16 * 4 + } + + #[allow(dead_code)] + fn decode_brr_block(encoded: [u8; 9], old: &mut f32, older: &mut f32) -> [i16; 16] { + // First block is a control block + let control = ControlBlock(encoded[0]); + + let mut decoded = [0; 16]; + + let control_filter = control.filter() as usize; + + // Samples are stored from encoded[1] to encoded[8] + for i in 0..16 { + // Sample values range from -8 to +7 + // and are stored high-endian. + let sample_byte = encoded[1 + (i / 2)]; + let nibble = (sample_byte >> (4 * ((i & 1) ^ 1))) & 0x0F; + + // Sign extend from 4 bits to 16 bits. + let raw_sample: i16 = (((nibble as i8) << 4) >> 4).into(); + + // Apply left shift value to convert to 15bit sample. + // TODO when shift amount is > 12, it seems it behaves as + // shift=12 and nibble=(nibble SAR 3) + let shifted_sample = (raw_sample << control.left_shift()) >> 1; + + // Apply control filter to the sample + let filtered = f32::from(shifted_sample) + + *old * FILTER_TABLE[control_filter][0] + + *older * FILTER_TABLE[control_filter][1]; + + *older = *old; + *old = filtered; + + decoded[i] = filtered as i16; + } + + decoded + } +} diff --git a/aliusnes/src/apu/mod.rs b/aliusnes/src/apu/mod.rs index 1394a15..d942c39 100644 --- a/aliusnes/src/apu/mod.rs +++ b/aliusnes/src/apu/mod.rs @@ -1 +1,175 @@ -pub mod spc700; +use crate::apu::dsp::Dsp; +use crate::apu::spc700::Spc700; +use crate::apu::spc700::timer::Timer; +use crate::bus::{Access, Address, Bus}; +use crate::cart::info::Model; +use crate::scheduler::{Event, Scheduler}; + +mod dsp; +mod spc700; + +#[cfg(feature = "apu")] +static IPL_ROM: &[u8; 0x40] = include_bytes!("spc700/ipl_boot.rom"); + +#[cfg(not(feature = "apu"))] +static IPL_ROM: &[u8; 0x40] = &[0; 0x40]; + +const NTSC_MASTER_CLOCK: u128 = 21_477_270; +const PAL_MASTER_CLOCK: u128 = 21_281_370; + +const SPC700_CLOCK: u128 = 1_024_000; + +const APU_EVENT_PERIOD_NTSC: u64 = ((32 * NTSC_MASTER_CLOCK) / SPC700_CLOCK) as u64; +const APU_EVENT_PERIOD_PAL: u64 = ((32 * PAL_MASTER_CLOCK) / SPC700_CLOCK) as u64; + +pub struct Apu { + spc700: Spc700, + bus: ApuBus, + model: Model, +} + +struct ApuBus { + aram: Box<[u8; 0x10000]>, + apuio: [u8; 4], + cpuio: [u8; 4], + bootrom_enabled: bool, + cycles: u64, + dsp: Dsp, + timers: [Timer; 3], +} + +impl Apu { + pub fn new(model: Model) -> Apu { + Apu { + spc700: Spc700::new(), + bus: ApuBus { + aram: vec![0; 0x10000].into_boxed_slice().try_into().unwrap(), + apuio: [0; 4], + cpuio: [0; 4], + bootrom_enabled: true, + cycles: 0, + dsp: Dsp::new(), + timers: [Timer::new(); 3], + }, + model, + } + } + + fn catch_up_to_master(&mut self, time: u64) { + let Apu { + ref mut bus, + ref mut spc700, + ref model, + } = self; + + let target_time = match model { + Model::Ntsc => u128::from(time) * SPC700_CLOCK / NTSC_MASTER_CLOCK, + Model::Pal => u128::from(time) * SPC700_CLOCK / PAL_MASTER_CLOCK, + }; + + while bus.cycles < target_time as u64 { + spc700.step(bus); + } + } + + pub(crate) fn handle_event(&mut self, scheduler: &mut Scheduler, time: u64) { + self.catch_up_to_master(time); + + // self.bus.dsp.output_sample(self.bus.aram.as_slice()); + + let period = match self.model { + Model::Ntsc => APU_EVENT_PERIOD_NTSC, + Model::Pal => APU_EVENT_PERIOD_PAL, + }; + scheduler.add_event(Event::Apu, time + period); + } +} + +impl Access for Apu { + fn read(&mut self, addr: u16, time: u64) -> Option { + self.catch_up_to_master(time); + Some(self.bus.cpuio[addr as usize & 3]) + } + + fn write(&mut self, addr: u16, data: u8, time: u64) { + self.catch_up_to_master(time); + self.bus.apuio[addr as usize & 3] = data; + } +} + +impl ApuBus { + fn write_control(&mut self, data: u8) { + for (i, timer) in self.timers.iter_mut().enumerate() { + timer.set_enabled(data & (1 << i) != 0); + } + + if (data & (1 << 4)) != 0 { + self.apuio[0..=1].fill(0); + } + if (data & (1 << 5)) != 0 { + self.apuio[2..=3].fill(0); + } + + self.bootrom_enabled = (data & 0x80) != 0; + } + + fn read(&self, addr: u16) -> u8 { + match addr { + // write-only area + 0x00F0 | 0x00F1 | 0x00FA..=0x00FC => { + println!("Attempted to read write-only registers, returning 0"); + 0 + }, + 0x00F2 => self.dsp.read_dsp_addr(), + 0x00F3 => self.dsp.read(), + 0x00F4..=0x00F7 => self.apuio[addr as usize & 3], + 0x00FD => self.timers[0].timer_output(), + 0x00FE => self.timers[1].timer_output(), + 0x00FF => self.timers[2].timer_output(), + 0xFFC0..=0xFFFF if self.bootrom_enabled => IPL_ROM[addr as usize & 0x3F], + _ => self.aram[addr as usize], + } + } + + fn write(&mut self, addr: u16, data: u8) { + match addr { + 0x00F0 => println!("Tried to write TEST: {data:#04x}"), + 0x00F1 => self.write_control(data), + 0x00F2 => self.dsp.set_dsp_addr(data), + 0x00F3 => self.dsp.write(data), + 0x00F4..=0x00F7 => { + self.cpuio[addr as usize & 3] = data; + }, + 0x00FA => self.timers[0].set_timer_target(data), + 0x00FB => self.timers[1].set_timer_target(data), + 0x00FC => self.timers[2].set_timer_target(data), + // read-only area + 0x00FD..=0x00FF => (), + _ => self.aram[addr as usize] = data, + } + } +} + +impl Bus for ApuBus { + fn read_and_tick(&mut self, addr: Address) -> u8 { + self.cycles += 1; + self.read(addr.offset) + } + + fn write_and_tick(&mut self, addr: Address, data: u8) { + self.cycles += 1; + self.write(addr.offset, data); + } + + fn add_io_cycles(&mut self, cycles: usize) { + self.cycles += cycles as u64; + } + + fn fired_nmi(&mut self) -> bool { + todo!() + } + + fn fired_irq(&mut self) -> bool { + todo!() + } +} diff --git a/aliusnes/src/apu/spc700/addressing.rs b/aliusnes/src/apu/spc700/addressing.rs index 4779cf8..2561420 100644 --- a/aliusnes/src/apu/spc700/addressing.rs +++ b/aliusnes/src/apu/spc700/addressing.rs @@ -134,7 +134,7 @@ impl Cpu { AddressingMode::Psw => self.status.0, AddressingMode::Immediate => self.get_imm(bus), AddressingMode::AbsoluteBooleanBit => { - let addr_bit = u16::from_le_bytes([self.get_imm(bus), self.get_imm(bus)]); + let addr_bit = self.abs(bus); let val = bus.read_and_tick((addr_bit & 0x1FFF).into()); val & 1 << (addr_bit >> 13) diff --git a/aliusnes/src/apu/spc700/cpu.rs b/aliusnes/src/apu/spc700/cpu.rs index 1f717c0..a22306d 100644 --- a/aliusnes/src/apu/spc700/cpu.rs +++ b/aliusnes/src/apu/spc700/cpu.rs @@ -31,7 +31,7 @@ impl Cpu { accumulator: 0x00, index_x: 0x00, index_y: 0x00, - program_counter: 0x00, + program_counter: 0xFFC0, stack_pointer: 0x00, status: Status(0), paused: false, @@ -47,7 +47,7 @@ impl Cpu { self.status.set_zero(value == 0); } - pub fn read_16(&mut self, bus: &mut B, addr: u16) -> u16 { + pub fn read_word(&self, bus: &mut B, addr: u16) -> u16 { u16::from_le_bytes([ bus.read_and_tick(addr.into()), bus.read_and_tick(addr.wrapping_add(1).into()), @@ -127,7 +127,7 @@ impl Cpu { AddressingMode::X => self.index_x = f(self, self.index_x), AddressingMode::Y => self.index_y = f(self, self.index_y), AddressingMode::AbsoluteBooleanBit => { - let addr_bit = u16::from_le_bytes([self.get_imm(bus), self.get_imm(bus)]); + let addr_bit = self.abs(bus); let page = addr_bit & 0x1FFF; let bit_pos = addr_bit >> 13; diff --git a/aliusnes/src/apu/spc700/instructions.rs b/aliusnes/src/apu/spc700/instructions.rs index 02d68f6..d6cd672 100644 --- a/aliusnes/src/apu/spc700/instructions.rs +++ b/aliusnes/src/apu/spc700/instructions.rs @@ -94,7 +94,7 @@ impl Spc700 { cpu.do_push(bus, cpu.program_counter.low_byte()); cpu.do_push(bus, cpu.status.0); - cpu.program_counter = cpu.read_16(bus, 0xFFDE); + cpu.program_counter = cpu.read_word(bus, 0xFFDE); cpu.status.set_irq_enabled(false); cpu.status.set_break_(true); } @@ -287,7 +287,7 @@ impl Spc700 { if let AddressingMode::Absolute = mode { cpu.program_counter = value; } else { - cpu.program_counter = cpu.read_16(bus, value); + cpu.program_counter = cpu.read_word(bus, value); } } @@ -532,7 +532,7 @@ impl Spc700 { cpu.do_push(bus, cpu.program_counter.low_byte()); let vector_addr = 0xFFDE - 2 * u16::from(INDEX); - cpu.program_counter = cpu.read_16(bus, vector_addr); + cpu.program_counter = cpu.read_word(bus, vector_addr); } pub fn tclr1(cpu: &mut Cpu, bus: &mut B, mode: AddressingMode) { diff --git a/aliusnes/src/apu/spc700/mod.rs b/aliusnes/src/apu/spc700/mod.rs index 247ce41..bcbe472 100644 --- a/aliusnes/src/apu/spc700/mod.rs +++ b/aliusnes/src/apu/spc700/mod.rs @@ -6,6 +6,7 @@ mod addressing; mod cpu; mod instructions; mod opcode; +pub(super) mod timer; pub(crate) struct Spc700 { cpu: Cpu, diff --git a/aliusnes/src/apu/spc700/opcode.rs b/aliusnes/src/apu/spc700/opcode.rs index 8ddeb4a..6a6ecb7 100644 --- a/aliusnes/src/apu/spc700/opcode.rs +++ b/aliusnes/src/apu/spc700/opcode.rs @@ -4,8 +4,10 @@ use crate::bus::Bus; #[derive(Clone, Copy)] pub(crate) struct Meta { + #[allow(dead_code)] pub code: u8, - pub mnemonic: &'static str, + #[allow(dead_code)] + mnemonic: &'static str, pub mode: AddressingMode, } diff --git a/aliusnes/src/apu/spc700/timer.rs b/aliusnes/src/apu/spc700/timer.rs new file mode 100644 index 0000000..d871571 --- /dev/null +++ b/aliusnes/src/apu/spc700/timer.rs @@ -0,0 +1,35 @@ +#[derive(Clone, Copy)] +pub struct Timer { + enabled: bool, + timer_target: u8, + timer_output: u8, +} + +impl Timer { + pub(crate) fn new() -> Timer { + Timer { + enabled: false, + timer_target: 0, + timer_output: 0, + } + } + + pub(crate) fn set_enabled(&mut self, enabled: bool) { + // A transition from clear to set (0 -> 1) will reset the timer's internal counter and + // TxOUT to 0. + // if !self.enabled && enabled {} + self.enabled = enabled; + } + + pub(crate) fn set_timer_target(&mut self, value: u8) { + // When enabled via $F1, the 3 timers will internally count at a rate of 8 KHz (timers 0,1) + // or 64 KHz (timer 2), and when this interval value has been exceeded, they will increment + // their external counter result ($FD-FF) and begin again. + self.timer_target = value; + self.timer_output = value; + } + + pub(crate) fn timer_output(&self) -> u8 { + self.timer_output + } +} diff --git a/aliusnes/src/bus/dma.rs b/aliusnes/src/bus/dma.rs index ccc45e0..539cb73 100644 --- a/aliusnes/src/bus/dma.rs +++ b/aliusnes/src/bus/dma.rs @@ -129,7 +129,7 @@ impl Access for Dma { } } - fn write(&mut self, addr: u16, data: u8) { + fn write(&mut self, addr: u16, data: u8, _: u64) { match addr { 0x420B => self.enable_channels = data, 0x420C => self.h_enable_channels = data, diff --git a/aliusnes/src/bus/math.rs b/aliusnes/src/bus/math.rs index 570007a..627f4d1 100644 --- a/aliusnes/src/bus/math.rs +++ b/aliusnes/src/bus/math.rs @@ -50,7 +50,7 @@ impl Access for Math { } } - fn write(&mut self, addr: u16, data: u8) { + fn write(&mut self, addr: u16, data: u8, _: u64) { match addr { 0x4202 => self.factor_a = data, 0x4203 => { diff --git a/aliusnes/src/bus/mod.rs b/aliusnes/src/bus/mod.rs index 6d4ae10..aaa925b 100644 --- a/aliusnes/src/bus/mod.rs +++ b/aliusnes/src/bus/mod.rs @@ -57,7 +57,10 @@ impl From
for usize { } pub(crate) trait Bus { - fn peek_at(&self, addr: Address) -> Option; + #[allow(dead_code)] + fn peek_at(&self, _addr: Address) -> Option { + None + } fn read_and_tick(&mut self, addr: Address) -> u8; fn write_and_tick(&mut self, addr: Address, data: u8); fn add_io_cycles(&mut self, cycles: usize); @@ -67,5 +70,5 @@ pub(crate) trait Bus { pub(crate) trait Access { fn read(&mut self, addr: u16, time: u64) -> Option; - fn write(&mut self, addr: u16, data: u8); + fn write(&mut self, addr: u16, data: u8, time: u64); } diff --git a/aliusnes/src/bus/system_bus.rs b/aliusnes/src/bus/system_bus.rs index c8f80c9..34c246b 100644 --- a/aliusnes/src/bus/system_bus.rs +++ b/aliusnes/src/bus/system_bus.rs @@ -1,3 +1,4 @@ +use crate::apu::Apu; use crate::bus::dma::Dma; use crate::bus::math::Math; use crate::bus::wram::Wram; @@ -10,13 +11,13 @@ use crate::utils::int_traits::ManipulateU16; pub struct SystemBus { mdr: u8, fast_rom_enabled: bool, + pub apu: Apu, cart: Cart, pub dma: Dma, math: Math, pub ppu: Ppu, pub scheduler: Scheduler, wram: Wram, - dummy_apu: [u8; 4], } impl SystemBus { @@ -24,13 +25,13 @@ impl SystemBus { Self { mdr: 0, fast_rom_enabled: false, + apu: Apu::new(cart.model), ppu: Ppu::new(cart.model), scheduler: Scheduler::new(), cart, dma: Dma::new(), math: Math::new(), wram: Wram::new(), - dummy_apu: [0xAA, 0, 0, 0], } } @@ -41,18 +42,7 @@ impl SystemBus { pub fn read_b(&mut self, addr: u16) -> u8 { if let Some(val) = match addr.low_byte() { 0x34..=0x3F => self.ppu.read(addr, self.scheduler.cycles), - 0x40..=0x43 => { - let ch = ((addr - 0x2140) % 4) as usize; - - let value = self.dummy_apu[ch]; - self.dummy_apu[ch] = match ch { - 0 => 0xAA, - 1 => 0xBB, - _ => 0, - }; - - Some(value) - }, + 0x40..=0x43 => self.apu.read(addr, self.scheduler.cycles), 0x80 => self.wram.read(addr, 0), _ => None, } { @@ -115,12 +105,9 @@ impl SystemBus { pub fn write_b(&mut self, addr: u16, data: u8) { match addr.low_byte() { - 0x00..=0x33 => self.ppu.write(addr, data), - 0x40..=0x43 => { - let ch = ((addr - 0x2140) % 4) as usize; - self.dummy_apu[ch] = data; - }, - 0x80..=0x83 => self.wram.write(addr, data), + 0x00..=0x33 => self.ppu.write(addr, data, 0), + 0x40..=0x43 => self.apu.write(addr, data, self.scheduler.cycles), + 0x80..=0x83 => self.wram.write(addr, data, 0), _ => println!("Tried to write at {addr:#0x} val: {data:#04x}"), } } @@ -136,12 +123,12 @@ impl SystemBus { 0x40..=0x43 if !DMA => { return match page { 0x4200 => self.ppu.write_nmitien(data), - 0x4202..=0x4206 => self.math.write(page, data), + 0x4202..=0x4206 => self.math.write(page, data, 0), 0x4207 => self.ppu.set_h_timer_low(data), 0x4208 => self.ppu.set_h_timer_high(data), 0x4209 => self.ppu.set_v_timer_low(data), 0x420A => self.ppu.set_v_timer_high(data), - 0x420B | 0x420C | 0x4300..=0x437f => self.dma.write(page, data), + 0x420B | 0x420C | 0x4300..=0x437f => self.dma.write(page, data, 0), 0x420D => self.fast_rom_enabled = data & 1 != 0, _ => println!("Tried to write at {page:#0x} val: {data:#04x}"), }; diff --git a/aliusnes/src/bus/wram.rs b/aliusnes/src/bus/wram.rs index 196af04..27db55b 100644 --- a/aliusnes/src/bus/wram.rs +++ b/aliusnes/src/bus/wram.rs @@ -45,7 +45,7 @@ impl Access for Wram { Some(data) } - fn write(&mut self, addr: u16, data: u8) { + fn write(&mut self, addr: u16, data: u8, _: u64) { match addr { 0x2180 => self.write_to_wm_addr(data), 0x2181 => self.wm_addl(data), diff --git a/aliusnes/src/emu.rs b/aliusnes/src/emu.rs index 2e0e418..388e8d0 100644 --- a/aliusnes/src/emu.rs +++ b/aliusnes/src/emu.rs @@ -22,6 +22,7 @@ impl Emu { emu.bus .scheduler .add_event(Event::Ppu(PpuEvent::NewScanline), 0); + emu.bus.scheduler.add_event(Event::Apu, 0); emu } @@ -44,6 +45,7 @@ impl Emu { while let Some((event, time)) = bus.scheduler.pop_event() { match event { Event::Ppu(ppu_event) => bus.ppu.handle_event(&mut bus.scheduler, ppu_event, time), + Event::Apu => bus.apu.handle_event(&mut bus.scheduler, time), } } } @@ -73,6 +75,7 @@ impl Emu { .ppu .handle_event(&mut self.bus.scheduler, ppu_event, time); }, + Event::Apu => self.bus.apu.handle_event(&mut self.bus.scheduler, time), } } } diff --git a/aliusnes/src/ppu.rs b/aliusnes/src/ppu.rs index 1ac7e02..bc1ccd6 100644 --- a/aliusnes/src/ppu.rs +++ b/aliusnes/src/ppu.rs @@ -191,7 +191,7 @@ impl Access for Ppu { } } - fn write(&mut self, addr: u16, data: u8) { + fn write(&mut self, addr: u16, data: u8, _: u64) { let nibble = addr.low_byte() as usize; match nibble { 0x00 => self.ini_display = IniDisplay(data), diff --git a/aliusnes/src/scheduler.rs b/aliusnes/src/scheduler.rs index 17a064e..929569e 100644 --- a/aliusnes/src/scheduler.rs +++ b/aliusnes/src/scheduler.rs @@ -9,6 +9,7 @@ pub enum PpuEvent { #[derive(Clone, Copy)] pub enum Event { + Apu, Ppu(PpuEvent), } @@ -16,6 +17,7 @@ impl Event { fn index(self) -> usize { match self { Event::Ppu(_) => 1, + Event::Apu => 2, } } } diff --git a/aliusnes/src/w65c816/cpu.rs b/aliusnes/src/w65c816/cpu.rs index 0f80812..a734553 100644 --- a/aliusnes/src/w65c816/cpu.rs +++ b/aliusnes/src/w65c816/cpu.rs @@ -6,6 +6,7 @@ use crate::w65c816::regsize::RegSize; pub enum Vector { Cop, Brk, + #[allow(dead_code)] Abort, Nmi, Irq, diff --git a/aliusnes/src/w65c816/opcode.rs b/aliusnes/src/w65c816/opcode.rs index 8686937..7a4b93e 100644 --- a/aliusnes/src/w65c816/opcode.rs +++ b/aliusnes/src/w65c816/opcode.rs @@ -6,7 +6,9 @@ use crate::w65c816::{Cpu, W65C816}; #[derive(Clone, Copy)] pub(crate) struct Meta { + #[allow(dead_code)] pub code: u8, + #[allow(dead_code)] mnemonic: &'static str, pub mode: AddressingMode, }