Compare commits
	
		
			95 Commits
		
	
	
		
			192bdffd64
			...
			refactor
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 9baa15050e | |||
| 4516ca8477 | |||
| 6087e3b20b | |||
| 10ac579c40 | |||
| ee5504111b | |||
| a628f64d28 | |||
| 318a6e0386 | |||
| db012c7f4b | |||
| e42c87aeb7 | |||
| 9113e95fa0 | |||
| 9973dc8714 | |||
| e128025208 | |||
| 44ac0c8ebd | |||
| 01064bab69 | |||
| 634bc2d2c0 | |||
| d794a94b68 | |||
| b87e31d3f4 | |||
| 3c2456611e | |||
| b829f05a34 | |||
| afd2d16371 | |||
| 1f8fa48168 | |||
| c6fbb79189 | |||
| 8b78b2943e | |||
| 0af95a1dd3 | |||
| 9fa40f8584 | |||
| b10bc7b4fd | |||
| 4658a0d106 | |||
| f92b9d61ef | |||
| 360a9a7b65 | |||
| 5d64e539a7 | |||
| 22f96a10e7 | |||
| 8fea8eb1ff | |||
| 9b2c91445a | |||
| b9046bceba | |||
| aa22e93049 | |||
| 6215eccb2f | |||
| a77d0a0f62 | |||
| c2f2e2194b | |||
| d68257bb29 | |||
| e27d6dc25b | |||
| 1acb5de19d | |||
| 1b78b248a3 | |||
| 5d6df46a2d | |||
| 7e65d82fef | |||
| 8c9567b610 | |||
| 53dfaf0de2 | |||
| 16c2dd81fc | |||
| 79be38a1e6 | |||
| 8625bec059 | |||
| 0637b771e3 | |||
| 0107fa04c9 | |||
| 6265c8af04 | |||
| 5482a8e75f | |||
| 002dae6826 | |||
| c863dc835c | |||
| d4407cf849 | |||
| de0d147685 | |||
| dd8339e8de | |||
| 05d6475015 | |||
| 32b597a328 | |||
| 33be2e0e83 | |||
| 832e1b7633 | |||
| a549b9feef | |||
| 4d6fc95130 | |||
| 7112cd15e3 | |||
| 2af673d7c5 | |||
| e43cac8041 | |||
| 8724da824e | |||
| 903cfacad3 | |||
| 9d0e099a97 | |||
| d9a3a7b0dd | |||
| 1a4546f4a4 | |||
| bd685a99a4 | |||
| 31abd6dc5c | |||
| c7e3cb5b35 | |||
| e4d77d66ba | |||
| 9e36e86c14 | |||
| 6f6c308d84 | |||
| f429d72882 | |||
| 4fda7587ce | |||
| 3fb182b9cb | |||
| c1430594cf | |||
| 8e2ef58b81 | |||
| b4b84d0cc5 | |||
| 1d8fb8d32a | |||
| cc0cb0dd8a | |||
| fd9a5b51ba | |||
| b9a798d0ad | |||
| a1eadc0bc4 | |||
| ea53f97312 | |||
| 08bd33b4d4 | |||
| 2b5926e533 | |||
| 1e27875dc3 | |||
| 1566d60fd4 | |||
| ab99e24f64 | 
							
								
								
									
										8
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								.vscode/extensions.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							| @@ -0,0 +1,8 @@ | ||||
| { | ||||
|     "recommendations": [ | ||||
|         "matklad.rust-analyzer", | ||||
|         "tamasfe.even-better-toml", | ||||
|         "serayuzgur.crates", | ||||
|         "vadimcn.vscode-lldb", | ||||
|     ] | ||||
| } | ||||
							
								
								
									
										1004
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										1004
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										12
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										12
									
								
								Cargo.toml
									
									
									
									
									
								
							| @@ -3,6 +3,7 @@ name = "gb" | ||||
| version = "0.1.0" | ||||
| authors = ["Rekai Musuka <rekai@musuka.dev>"] | ||||
| edition = "2018" | ||||
| resolver = "2" | ||||
|  | ||||
| # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||||
|  | ||||
| @@ -11,14 +12,11 @@ anyhow = "^1.0" | ||||
| bitfield = "^0.13" | ||||
| clap = "^2.33" | ||||
| gilrs = "^0.8" | ||||
| pixels = "^0.3" | ||||
| winit = "^0.24" | ||||
| winit_input_helper = "^0.9" | ||||
| egui = "^0.10" | ||||
| egui_wgpu_backend = { git="https://github.com/hasenbanck/egui_wgpu_backend.git", rev="9d03ad345d15d1e44165849b242d3562fdf3e859" } | ||||
| egui_winit_platform = { git="https://github.com/hasenbanck/egui_winit_platform.git", rev="17298250e9721e8bf2c1d4a17b3e22777f8cb2e8" } | ||||
| pixels = "^0.6" | ||||
| winit = "^0.25" | ||||
| winit_input_helper = "^0.10" | ||||
| rodio = "^0.14" | ||||
| crossbeam-channel = "^0.5" | ||||
| rtrb = "^0.1.4" | ||||
|  | ||||
| [profile.release] | ||||
| debug = true | ||||
|   | ||||
							
								
								
									
										938
									
								
								src/apu.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										938
									
								
								src/apu.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,938 @@ | ||||
| use crate::bus::BusIo; | ||||
| use crate::emu::SM83_CLOCK_SPEED; | ||||
| use gen::SampleProducer; | ||||
| use types::ch1::{Sweep, SweepDirection}; | ||||
| use types::ch3::Volume as Ch3Volume; | ||||
| use types::ch4::{CounterWidth, Frequency as Ch4Frequency, PolynomialCounter}; | ||||
| use types::common::{EnvelopeDirection, FrequencyHigh, SoundDuty, VolumeEnvelope}; | ||||
| use types::fs::{FrameSequencer, State as FrameSequencerState}; | ||||
| use types::{ChannelControl, SoundOutput}; | ||||
|  | ||||
| pub mod gen; | ||||
| mod types; | ||||
|  | ||||
| const SAMPLE_INCREMENT: u64 = gen::SAMPLE_RATE as u64; | ||||
| const WAVE_PATTERN_RAM_LEN: usize = 0x10; | ||||
|  | ||||
| #[derive(Default, Debug)] | ||||
| pub struct Apu { | ||||
|     ctrl: SoundControl, | ||||
|     /// Tone & Sweep | ||||
|     ch1: Channel1, | ||||
|     /// Tone | ||||
|     ch2: Channel2, | ||||
|     /// Wave | ||||
|     ch3: Channel3, | ||||
|     /// Noise | ||||
|     ch4: Channel4, | ||||
|  | ||||
|     sequencer: FrameSequencer, | ||||
|     div_prev: Option<u16>, | ||||
|  | ||||
|     prod: Option<SampleProducer<f32>>, | ||||
|     sample_counter: u64, | ||||
| } | ||||
|  | ||||
| impl BusIo for Apu { | ||||
|     fn read_byte(&self, addr: u16) -> u8 { | ||||
|         match addr & 0x00FF { | ||||
|             0x10 => self.ch1.sweep(), | ||||
|             0x11 => self.ch1.duty(), | ||||
|             0x12 => self.ch1.envelope(), | ||||
|             0x14 => self.ch1.freq_hi(), | ||||
|             0x16 => self.ch2.duty(), | ||||
|             0x17 => self.ch2.envelope(), | ||||
|             0x19 => self.ch2.freq_hi(), | ||||
|             0x1A => self.ch3.dac_enabled(), | ||||
|             0x1C => self.ch3.volume(), | ||||
|             0x1E => self.ch3.freq_hi(), | ||||
|             0x21 => self.ch4.envelope(), | ||||
|             0x22 => self.ch4.poly(), | ||||
|             0x23 => self.ch4.frequency(), | ||||
|             0x24 => self.ctrl.channel(), | ||||
|             0x25 => self.ctrl.output(), | ||||
|             0x26 => self.ctrl.status(self), | ||||
|             0x30..=0x3F => self.ch3.read_byte(addr), | ||||
|             _ => { | ||||
|                 eprintln!("Read 0xFF from unused IO register {:#06X} [APU]", addr); | ||||
|                 0xFF | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn write_byte(&mut self, addr: u16, byte: u8) { | ||||
|         match addr & 0x00FF { | ||||
|             0x10 if self.ctrl.enabled => self.ch1.set_sweep(byte), | ||||
|             0x11 if self.ctrl.enabled => self.ch1.set_duty(byte), | ||||
|             0x12 if self.ctrl.enabled => self.ch1.set_envelope(byte), | ||||
|             0x13 if self.ctrl.enabled => self.ch1.set_freq_lo(byte), | ||||
|             0x14 if self.ctrl.enabled => self.ch1.set_freq_hi(byte), | ||||
|             0x16 if self.ctrl.enabled => self.ch2.set_duty(byte), | ||||
|             0x17 if self.ctrl.enabled => self.ch2.set_envelope(byte), | ||||
|             0x18 if self.ctrl.enabled => self.ch2.set_freq_lo(byte), | ||||
|             0x19 if self.ctrl.enabled => self.ch2.set_freq_hi(byte), | ||||
|             0x1A if self.ctrl.enabled => self.ch3.set_dac_enabled(byte), | ||||
|             0x1B if self.ctrl.enabled => self.ch3.set_len(byte), | ||||
|             0x1C if self.ctrl.enabled => self.ch3.set_volume(byte), | ||||
|             0x1D if self.ctrl.enabled => self.ch3.set_freq_lo(byte), | ||||
|             0x1E if self.ctrl.enabled => self.ch3.set_freq_hi(byte), | ||||
|             0x20 if self.ctrl.enabled => self.ch4.set_len(byte), | ||||
|             0x21 if self.ctrl.enabled => self.ch4.set_envelope(byte), | ||||
|             0x22 if self.ctrl.enabled => self.ch4.set_poly(byte), | ||||
|             0x23 if self.ctrl.enabled => self.ch4.set_frequency(byte), | ||||
|             0x24 if self.ctrl.enabled => self.ctrl.set_channel(byte), | ||||
|             0x25 if self.ctrl.enabled => self.ctrl.set_output(byte), | ||||
|             0x26 => self.set_status(byte), | ||||
|             0x30..=0x3F => self.ch3.write_byte(addr, byte), | ||||
|             _ if !self.ctrl.enabled => {} | ||||
|             _ => eprintln!( | ||||
|                 "Wrote {:#04X} to unused IO register {:#06X} [APU]", | ||||
|                 byte, addr | ||||
|             ), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Apu { | ||||
|     pub(crate) fn tick(&mut self, div: u16) { | ||||
|         self.sample_counter += SAMPLE_INCREMENT; | ||||
|  | ||||
|         // Frame Sequencer (512Hz) | ||||
|         if self.is_falling_edge(12, div) { | ||||
|             use FrameSequencerState::*; | ||||
|  | ||||
|             match self.sequencer.state() { | ||||
|                 Length => self.clock_length(), | ||||
|                 LengthAndSweep => { | ||||
|                     self.clock_length(); | ||||
|                     self.clock_sweep(); | ||||
|                 } | ||||
|                 Envelope => self.clock_envelope(), | ||||
|                 Nothing => {} | ||||
|             } | ||||
|  | ||||
|             self.sequencer.next(); | ||||
|         } | ||||
|  | ||||
|         self.div_prev = Some(div); | ||||
|  | ||||
|         self.ch1.tick(); | ||||
|         self.ch2.tick(); | ||||
|         self.ch3.tick(); | ||||
|         self.ch4.tick(); | ||||
|  | ||||
|         if self.sample_counter >= SM83_CLOCK_SPEED { | ||||
|             self.sample_counter %= SM83_CLOCK_SPEED; | ||||
|  | ||||
|             if let Some(ref mut prod) = self.prod { | ||||
|                 if prod.available_blocking() { | ||||
|                     // Sample the APU | ||||
|  | ||||
|                     let (left, right) = self.ctrl.out.ch1(); | ||||
|                     let ch1_left = if left { self.ch1.amplitude() } else { 0.0 }; | ||||
|                     let ch1_right = if right { self.ch1.amplitude() } else { 0.0 }; | ||||
|  | ||||
|                     let (left, right) = self.ctrl.out.ch2(); | ||||
|                     let ch2_left = if left { self.ch2.amplitude() } else { 0.0 }; | ||||
|                     let ch2_right = if right { self.ch2.amplitude() } else { 0.0 }; | ||||
|  | ||||
|                     let (left, right) = self.ctrl.out.ch3(); | ||||
|                     let ch3_left = if left { self.ch3.amplitude() } else { 0.0 }; | ||||
|                     let ch3_right = if right { self.ch3.amplitude() } else { 0.0 }; | ||||
|  | ||||
|                     let (left, right) = self.ctrl.out.ch4(); | ||||
|                     let ch4_left = if left { self.ch4.amplitude() } else { 0.0 }; | ||||
|                     let ch4_right = if right { self.ch4.amplitude() } else { 0.0 }; | ||||
|  | ||||
|                     let left_mixed = (ch1_left + ch2_left + ch3_left + ch4_left) / 4.0; | ||||
|                     let right_mixed = (ch1_right + ch2_right + ch3_right + ch4_right) / 4.0; | ||||
|  | ||||
|                     let left_sample = (self.ctrl.channel.left_volume() + 1.0) * left_mixed; | ||||
|                     let right_sample = (self.ctrl.channel.right_volume() + 1.0) * right_mixed; | ||||
|  | ||||
|                     prod.push(left_sample) | ||||
|                         .and(prod.push(right_sample)) | ||||
|                         .expect("Add samples to ring buffer"); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// 0xFF26 | NR52 - Sound On/Off | ||||
|     pub(crate) fn set_status(&mut self, byte: u8) { | ||||
|         self.ctrl.enabled = (byte >> 7) & 0x01 == 0x01; | ||||
|  | ||||
|         if self.ctrl.enabled { | ||||
|             // Frame Sequencer reset to Step 0 | ||||
|             self.sequencer.reset(); | ||||
|  | ||||
|             // Square Duty units are reset to first step | ||||
|             self.ch1.duty_pos = 0; | ||||
|             self.ch2.duty_pos = 0; | ||||
|  | ||||
|             // Wave Channel's sample buffer reset to 0 | ||||
|             self.ch3.offset = 0; | ||||
|         } | ||||
|  | ||||
|         if !self.ctrl.enabled { | ||||
|             self.reset(); | ||||
|         } else { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn attach_producer(&mut self, prod: SampleProducer<f32>) { | ||||
|         self.prod = Some(prod); | ||||
|     } | ||||
|  | ||||
|     fn reset(&mut self) { | ||||
|         self.ch1.sweep = Default::default(); | ||||
|         self.ch1.duty = Default::default(); | ||||
|         self.ch1.envelope = Default::default(); | ||||
|         self.ch1.freq_lo = Default::default(); | ||||
|         self.ch1.freq_hi = Default::default(); | ||||
|  | ||||
|         self.ch2.duty = Default::default(); | ||||
|         self.ch2.envelope = Default::default(); | ||||
|         self.ch2.freq_lo = Default::default(); | ||||
|         self.ch2.freq_hi = Default::default(); | ||||
|  | ||||
|         self.ch3.dac_enabled = Default::default(); | ||||
|         self.ch3.len = Default::default(); | ||||
|         self.ch3.volume = Default::default(); | ||||
|         self.ch3.freq_lo = Default::default(); | ||||
|         self.ch3.freq_hi = Default::default(); | ||||
|  | ||||
|         self.ch4.len = Default::default(); | ||||
|         self.ch4.envelope = Default::default(); | ||||
|         self.ch4.poly = Default::default(); | ||||
|         self.ch4.freq = Default::default(); | ||||
|  | ||||
|         self.ctrl.channel = Default::default(); | ||||
|         self.ctrl.out = Default::default(); | ||||
|  | ||||
|         // Disable the Channels | ||||
|         self.ch1.enabled = Default::default(); | ||||
|         self.ch2.enabled = Default::default(); | ||||
|         self.ch3.enabled = Default::default(); | ||||
|         self.ch4.enabled = Default::default(); | ||||
|     } | ||||
|  | ||||
|     fn process_length(freq_hi: &FrequencyHigh, length_timer: &mut u16, enabled: &mut bool) { | ||||
|         if freq_hi.length_disable() && *length_timer > 0 { | ||||
|             *length_timer -= 1; | ||||
|  | ||||
|             // Check in this scope ensures (only) the above subtraction | ||||
|             // made length_timer 0 | ||||
|             if *length_timer == 0 { | ||||
|                 *enabled = false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn ch4_process_length(freq: &Ch4Frequency, length_timer: &mut u16, enabled: &mut bool) { | ||||
|         if freq.length_disable() && *length_timer > 0 { | ||||
|             *length_timer -= 1; | ||||
|  | ||||
|             // Check in this scope ensures (only) the above subtraction | ||||
|             // made length_timer 0 | ||||
|             if *length_timer == 0 { | ||||
|                 *enabled = false; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn clock_length(&mut self) { | ||||
|         Self::process_length( | ||||
|             &self.ch1.freq_hi, | ||||
|             &mut self.ch1.length_timer, | ||||
|             &mut self.ch1.enabled, | ||||
|         ); | ||||
|  | ||||
|         Self::process_length( | ||||
|             &self.ch2.freq_hi, | ||||
|             &mut self.ch2.length_timer, | ||||
|             &mut self.ch2.enabled, | ||||
|         ); | ||||
|  | ||||
|         Self::process_length( | ||||
|             &self.ch3.freq_hi, | ||||
|             &mut self.ch3.length_timer, | ||||
|             &mut self.ch3.enabled, | ||||
|         ); | ||||
|  | ||||
|         Self::ch4_process_length( | ||||
|             &self.ch4.freq, | ||||
|             &mut self.ch4.length_timer, | ||||
|             &mut self.ch4.enabled, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     fn clock_sweep(&mut self) { | ||||
|         if self.ch1.sweep_timer != 0 { | ||||
|             self.ch1.sweep_timer -= 1; | ||||
|         } | ||||
|  | ||||
|         if self.ch1.sweep_timer == 0 { | ||||
|             let period = self.ch1.sweep.period(); | ||||
|             self.ch1.sweep_timer = if period == 0 { 8 } else { period }; | ||||
|  | ||||
|             if self.ch1.sweep_enabled && period != 0 { | ||||
|                 let new_freq = self.ch1.calc_sweep_freq(); | ||||
|  | ||||
|                 if new_freq <= 2047 && self.ch1.sweep.shift_count() != 0 { | ||||
|                     self.ch1.set_frequency(new_freq); | ||||
|                     self.ch1.shadow_freq = new_freq; | ||||
|  | ||||
|                     let _ = self.ch1.calc_sweep_freq(); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn process_envelope(envelope: &VolumeEnvelope, period_timer: &mut u8, current_volume: &mut u8) { | ||||
|         use EnvelopeDirection::*; | ||||
|  | ||||
|         if envelope.period() != 0 { | ||||
|             if *period_timer != 0 { | ||||
|                 *period_timer -= 1; | ||||
|             } | ||||
|  | ||||
|             if *period_timer == 0 { | ||||
|                 *period_timer = envelope.period(); | ||||
|  | ||||
|                 match envelope.direction() { | ||||
|                     Decrease if *current_volume > 0x00 => *current_volume -= 1, | ||||
|                     Increase if *current_volume < 0x0F => *current_volume += 1, | ||||
|                     _ => {} | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn clock_envelope(&mut self) { | ||||
|         // Channels 1, 2 and 4 have Volume Envelopes | ||||
|  | ||||
|         Self::process_envelope( | ||||
|             &self.ch1.envelope, | ||||
|             &mut self.ch1.period_timer, | ||||
|             &mut self.ch1.current_volume, | ||||
|         ); | ||||
|  | ||||
|         Self::process_envelope( | ||||
|             &self.ch2.envelope, | ||||
|             &mut self.ch2.period_timer, | ||||
|             &mut self.ch2.current_volume, | ||||
|         ); | ||||
|  | ||||
|         Self::process_envelope( | ||||
|             &self.ch4.envelope, | ||||
|             &mut self.ch4.period_timer, | ||||
|             &mut self.ch4.current_volume, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     fn is_falling_edge(&self, bit: u8, div: u16) -> bool { | ||||
|         match self.div_prev { | ||||
|             Some(p) => (p >> bit & 0x01) == 0x01 && (div >> bit & 0x01) == 0x00, | ||||
|             None => false, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| pub(crate) struct SoundControl { | ||||
|     /// 0xFF24 | NR50 - Channel Control | ||||
|     channel: ChannelControl, | ||||
|     /// 0xFF25 | NR51 - Selection of Sound output terminal | ||||
|     out: SoundOutput, | ||||
|  | ||||
|     enabled: bool, | ||||
| } | ||||
|  | ||||
| impl SoundControl { | ||||
|     /// 0xFF24 | NR50 - Channel Control | ||||
|     pub(crate) fn channel(&self) -> u8 { | ||||
|         u8::from(self.channel) | ||||
|     } | ||||
|  | ||||
|     /// 0xFF24 | NR50 - Channel Control | ||||
|     pub(crate) fn set_channel(&mut self, byte: u8) { | ||||
|         if self.enabled { | ||||
|             self.channel = byte.into(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// 0xFF25 | NR51 - Selection of Sound output terminal | ||||
|     pub(crate) fn output(&self) -> u8 { | ||||
|         u8::from(self.out) | ||||
|     } | ||||
|  | ||||
|     /// 0xFF25 | NR51 - Selection of Sound output terminal | ||||
|     pub(crate) fn set_output(&mut self, byte: u8) { | ||||
|         if self.enabled { | ||||
|             self.out = byte.into(); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// 0xFF26 | NR52 - Sound On/Off | ||||
|     pub(crate) fn status(&self, apu: &Apu) -> u8 { | ||||
|         (self.enabled as u8) << 7 | ||||
|             | (apu.ch4.enabled as u8) << 3 | ||||
|             | (apu.ch3.enabled as u8) << 2 | ||||
|             | (apu.ch2.enabled as u8) << 1 | ||||
|             | apu.ch1.enabled as u8 | ||||
|             | 0x70 | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| pub(crate) struct Channel1 { | ||||
|     /// 0xFF10 | NR10 - Channel 1 Sweep Register | ||||
|     sweep: Sweep, | ||||
|     /// 0xFF11 | NR11 - Channel 1 Sound length / Wave pattern duty | ||||
|     duty: SoundDuty, | ||||
|     /// 0xFF12 | NR12 - Channel 1 Volume Envelope | ||||
|     envelope: VolumeEnvelope, | ||||
|     /// 0xFF13 | NR13 - Channel 1 Frequency low (lower 8 bits only) | ||||
|     freq_lo: u8, | ||||
|     /// 0xFF14 | NR14 - Channel 1 Frequency high | ||||
|     freq_hi: FrequencyHigh, | ||||
|  | ||||
|     // Envelope Functionality | ||||
|     period_timer: u8, | ||||
|     current_volume: u8, | ||||
|  | ||||
|     // Sweep Functionality | ||||
|     sweep_timer: u8, | ||||
|     shadow_freq: u16, | ||||
|     sweep_enabled: bool, | ||||
|  | ||||
|     // Length Functionality | ||||
|     length_timer: u16, | ||||
|  | ||||
|     freq_timer: u16, | ||||
|     duty_pos: u8, | ||||
|  | ||||
|     enabled: bool, | ||||
| } | ||||
|  | ||||
| impl Channel1 { | ||||
|     /// 0xFF10 | NR10 - Channel 1 Sweep Register | ||||
|     pub(crate) fn sweep(&self) -> u8 { | ||||
|         u8::from(self.sweep) | 0x80 | ||||
|     } | ||||
|  | ||||
|     /// 0xFF10 | NR10 - Channel 1 Sweep Register | ||||
|     pub(crate) fn set_sweep(&mut self, byte: u8) { | ||||
|         self.sweep = byte.into() | ||||
|     } | ||||
|  | ||||
|     /// 0xFF11 | NR11 - Channel 1 Sound length / Wave pattern duty | ||||
|     pub(crate) fn duty(&self) -> u8 { | ||||
|         u8::from(self.duty) | 0x3F | ||||
|     } | ||||
|  | ||||
|     /// 0xFF11 | NR11 - Channel 1 Sound length / Wave pattern duty | ||||
|     pub(crate) fn set_duty(&mut self, byte: u8) { | ||||
|         self.duty = byte.into(); | ||||
|         self.length_timer = 64 - self.duty.sound_length() as u16; | ||||
|     } | ||||
|  | ||||
|     /// 0xFF12 | NR12 - Channel 1 Volume Envelope | ||||
|     pub fn envelope(&self) -> u8 { | ||||
|         u8::from(self.envelope) | ||||
|     } | ||||
|  | ||||
|     /// 0xFF12 | NR12 - Channel 1 Volume Envelope | ||||
|     pub(crate) fn set_envelope(&mut self, byte: u8) { | ||||
|         self.envelope = byte.into(); | ||||
|  | ||||
|         if !self.is_dac_enabled() { | ||||
|             self.enabled = false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// 0xFF13 | NR13 - Channel 1 Frequency low (lower 8 bits only) | ||||
|     pub(crate) fn set_freq_lo(&mut self, byte: u8) { | ||||
|         self.freq_lo = byte; | ||||
|     } | ||||
|  | ||||
|     /// 0xFF14 | NR14 - Channel 1 Frequency high | ||||
|     pub(crate) fn freq_hi(&self) -> u8 { | ||||
|         u8::from(self.freq_hi) | 0xBF | ||||
|     } | ||||
|  | ||||
|     /// 0xFF14 | NR14 - Channel 1 Frequency high | ||||
|     pub(crate) fn set_freq_hi(&mut self, byte: u8) { | ||||
|         self.freq_hi = byte.into(); | ||||
|  | ||||
|         // If this bit is set, a trigger event occurs | ||||
|         if self.freq_hi.initial() { | ||||
|             if self.is_dac_enabled() { | ||||
|                 self.enabled = true; | ||||
|             } | ||||
|  | ||||
|             // Length behaviour during trigger event | ||||
|             if self.length_timer == 0 { | ||||
|                 self.length_timer = 64; | ||||
|             } | ||||
|  | ||||
|             // Envelope Behaviour during trigger event | ||||
|             self.period_timer = self.envelope.period(); | ||||
|             self.current_volume = self.envelope.init_vol(); | ||||
|  | ||||
|             // Sweep behaviour during trigger event | ||||
|             let sweep_period = self.sweep.period(); | ||||
|             let sweep_shift = self.sweep.shift_count(); | ||||
|  | ||||
|             self.shadow_freq = self.frequency(); | ||||
|             self.sweep_timer = if sweep_period == 0 { 8 } else { sweep_period }; | ||||
|  | ||||
|             self.sweep_enabled = sweep_period != 0 || sweep_shift != 0; | ||||
|  | ||||
|             if sweep_shift != 0 { | ||||
|                 let _ = self.calc_sweep_freq(); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn tick(&mut self) { | ||||
|         if self.freq_timer != 0 { | ||||
|             self.freq_timer -= 1; | ||||
|         } | ||||
|  | ||||
|         if self.freq_timer == 0 { | ||||
|             self.freq_timer = (2048 - self.frequency()) * 4; | ||||
|             self.duty_pos = (self.duty_pos + 1) % 8; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn amplitude(&self) -> f32 { | ||||
|         if self.is_dac_enabled() { | ||||
|             let sample = self.duty.wave_pattern().amplitude(self.duty_pos) * self.current_volume; | ||||
|             let input = if self.enabled { sample } else { 0 }; | ||||
|  | ||||
|             (input as f32 / 7.5) - 1.0 | ||||
|         } else { | ||||
|             0.0 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn calc_sweep_freq(&mut self) -> u16 { | ||||
|         use SweepDirection::*; | ||||
|  | ||||
|         let shadow_freq_shifted = self.shadow_freq >> self.sweep.shift_count(); | ||||
|         let new_freq = match self.sweep.direction() { | ||||
|             Increase => self.shadow_freq + shadow_freq_shifted, | ||||
|             Decrease => self.shadow_freq - shadow_freq_shifted, | ||||
|         }; | ||||
|  | ||||
|         // Overflow check | ||||
|         if new_freq > 2047 { | ||||
|             self.enabled = false; | ||||
|         } | ||||
|  | ||||
|         new_freq | ||||
|     } | ||||
|  | ||||
|     fn set_frequency(&mut self, word: u16) { | ||||
|         let freq_bits = word & 0x07FF; | ||||
|         self.freq_lo = (freq_bits & 0x00FF) as u8; | ||||
|         self.freq_hi | ||||
|             .set_freq_bits(((freq_bits & 0x0700) >> 8) as u8); | ||||
|     } | ||||
|  | ||||
|     fn frequency(&self) -> u16 { | ||||
|         (self.freq_hi.freq_bits() as u16) << 8 | self.freq_lo as u16 | ||||
|     } | ||||
|  | ||||
|     fn is_dac_enabled(&self) -> bool { | ||||
|         self.envelope.0 & 0xF8 != 0x00 | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| pub(crate) struct Channel2 { | ||||
|     /// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty | ||||
|     duty: SoundDuty, | ||||
|     /// 0xFF17 | NR22 - Channel 2 Volume ENvelope | ||||
|     envelope: VolumeEnvelope, | ||||
|     /// 0xFF18 | NR23 - Channel 2 Frequency low (lower 8 bits only) | ||||
|     freq_lo: u8, | ||||
|     /// 0xFF19 | NR24 - Channel 2 Frequency high | ||||
|     freq_hi: FrequencyHigh, | ||||
|  | ||||
|     // Envelope Functionality | ||||
|     period_timer: u8, | ||||
|     current_volume: u8, | ||||
|  | ||||
|     // Length Functionality | ||||
|     length_timer: u16, | ||||
|  | ||||
|     freq_timer: u16, | ||||
|     duty_pos: u8, | ||||
|  | ||||
|     enabled: bool, | ||||
| } | ||||
|  | ||||
| impl Channel2 { | ||||
|     /// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty | ||||
|     pub(crate) fn duty(&self) -> u8 { | ||||
|         u8::from(self.duty) | 0x3F | ||||
|     } | ||||
|  | ||||
|     /// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty | ||||
|     pub(crate) fn set_duty(&mut self, byte: u8) { | ||||
|         self.duty = byte.into(); | ||||
|         self.length_timer = 64 - self.duty.sound_length() as u16; | ||||
|     } | ||||
|  | ||||
|     /// 0xFF17 | NR22 - Channel 2 Volume ENvelope | ||||
|     pub(crate) fn envelope(&self) -> u8 { | ||||
|         u8::from(self.envelope) | ||||
|     } | ||||
|  | ||||
|     /// 0xFF17 | NR22 - Channel 2 Volume ENvelope | ||||
|     pub(crate) fn set_envelope(&mut self, byte: u8) { | ||||
|         self.envelope = byte.into(); | ||||
|  | ||||
|         if !self.is_dac_enabled() { | ||||
|             self.enabled = false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// 0xFF18 | NR23 - Channel 2 Frequency low (lower 8 bits only) | ||||
|     pub(crate) fn set_freq_lo(&mut self, byte: u8) { | ||||
|         self.freq_lo = byte; | ||||
|     } | ||||
|  | ||||
|     /// 0xFF19 | NR24 - Channel 2 Frequency high | ||||
|     pub(crate) fn freq_hi(&self) -> u8 { | ||||
|         u8::from(self.freq_hi) | 0xBF | ||||
|     } | ||||
|  | ||||
|     /// 0xFF19 | NR24 - Channel 2 Frequency high | ||||
|     pub(crate) fn set_freq_hi(&mut self, byte: u8) { | ||||
|         self.freq_hi = byte.into(); | ||||
|  | ||||
|         if self.freq_hi.initial() { | ||||
|             // Envelope behaviour during trigger event | ||||
|             self.period_timer = self.envelope.period(); | ||||
|             self.current_volume = self.envelope.init_vol(); | ||||
|  | ||||
|             // Length behaviour during trigger event | ||||
|             if self.length_timer == 0 { | ||||
|                 self.length_timer = 64; | ||||
|             } | ||||
|  | ||||
|             if self.is_dac_enabled() { | ||||
|                 self.enabled = true; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn amplitude(&self) -> f32 { | ||||
|         if self.is_dac_enabled() { | ||||
|             let sample = self.duty.wave_pattern().amplitude(self.duty_pos) * self.current_volume; | ||||
|             let input = if self.enabled { sample } else { 0 }; | ||||
|  | ||||
|             (input as f32 / 7.5) - 1.0 | ||||
|         } else { | ||||
|             0.0 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn tick(&mut self) { | ||||
|         if self.freq_timer != 0 { | ||||
|             self.freq_timer -= 1; | ||||
|         } | ||||
|  | ||||
|         if self.freq_timer == 0 { | ||||
|             self.freq_timer = (2048 - self.frequency()) * 4; | ||||
|             self.duty_pos = (self.duty_pos + 1) % 8; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn frequency(&self) -> u16 { | ||||
|         (self.freq_hi.freq_bits() as u16) << 8 | self.freq_lo as u16 | ||||
|     } | ||||
|  | ||||
|     fn is_dac_enabled(&self) -> bool { | ||||
|         self.envelope.0 & 0xF8 != 0x00 | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| pub(crate) struct Channel3 { | ||||
|     /// 0xFF1A | NR30 - Channel 3 Sound on/off | ||||
|     dac_enabled: bool, | ||||
|     /// 0xFF1B | NR31 - Sound Length | ||||
|     len: u8, | ||||
|     /// 0xFF1C | NR32 - Channel 3 Volume | ||||
|     volume: Ch3Volume, | ||||
|     /// 0xFF1D | NR33 - Channel 3 Frequency low (lower 8 bits) | ||||
|     freq_lo: u8, | ||||
|     /// 0xFF1E | NR34 - Channel 3 Frequency high | ||||
|     freq_hi: FrequencyHigh, | ||||
|  | ||||
|     wave_ram: [u8; WAVE_PATTERN_RAM_LEN], | ||||
|  | ||||
|     // Length Functionality | ||||
|     length_timer: u16, | ||||
|  | ||||
|     freq_timer: u16, | ||||
|     offset: u8, | ||||
|  | ||||
|     enabled: bool, | ||||
| } | ||||
|  | ||||
| impl BusIo for Channel3 { | ||||
|     fn read_byte(&self, addr: u16) -> u8 { | ||||
|         if self.enabled { | ||||
|             self.wave_ram[self.offset as usize / 2] | ||||
|         } else { | ||||
|             self.wave_ram[(addr - Self::WAVE_RAM_START_ADDR) as usize] | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn write_byte(&mut self, addr: u16, byte: u8) { | ||||
|         if self.enabled { | ||||
|             self.wave_ram[self.offset as usize / 2] = byte; | ||||
|         } else { | ||||
|             self.wave_ram[(addr - Self::WAVE_RAM_START_ADDR) as usize] = byte; | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Channel3 { | ||||
|     const WAVE_RAM_START_ADDR: u16 = 0xFF30; | ||||
|  | ||||
|     /// 0xFF1A | NR30 - Channel 3 Sound on/off | ||||
|     pub(crate) fn dac_enabled(&self) -> u8 { | ||||
|         ((self.dac_enabled as u8) << 7) | 0x7F | ||||
|     } | ||||
|  | ||||
|     /// 0xFF1A | NR30 - Channel 3 Sound on/off | ||||
|     pub(crate) fn set_dac_enabled(&mut self, byte: u8) { | ||||
|         self.dac_enabled = (byte >> 7) & 0x01 == 0x01; | ||||
|  | ||||
|         if !self.dac_enabled { | ||||
|             self.enabled = false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// 0xFF1B | NR31 - Sound Length | ||||
|     pub(crate) fn set_len(&mut self, byte: u8) { | ||||
|         self.len = byte; | ||||
|         self.length_timer = 256 - self.len as u16; | ||||
|     } | ||||
|  | ||||
|     /// 0xFF1C | NR32 - Channel 3 Volume | ||||
|     pub(crate) fn volume(&self) -> u8 { | ||||
|         ((self.volume as u8) << 5) | 0x9F | ||||
|     } | ||||
|  | ||||
|     /// 0xFF1C | NR32 - Channel 3 Volume | ||||
|     pub(crate) fn set_volume(&mut self, byte: u8) { | ||||
|         use Ch3Volume::*; | ||||
|  | ||||
|         self.volume = match (byte >> 5) & 0x03 { | ||||
|             0b00 => Mute, | ||||
|             0b01 => Full, | ||||
|             0b10 => Half, | ||||
|             0b11 => Quarter, | ||||
|             _ => unreachable!("{:#04X} is not a valid value for Channel3Volume", byte), | ||||
|         }; | ||||
|     } | ||||
|  | ||||
|     /// 0xFF1D | NR33 - Channel 3 Frequency low (lower 8 bits) | ||||
|     pub(crate) fn set_freq_lo(&mut self, byte: u8) { | ||||
|         self.freq_lo = byte; | ||||
|     } | ||||
|  | ||||
|     /// 0xFF1E | NR34 - Channel 3 Frequency high | ||||
|     pub(crate) fn freq_hi(&self) -> u8 { | ||||
|         u8::from(self.freq_hi) | 0xBF | ||||
|     } | ||||
|  | ||||
|     /// 0xFF1E | NR34 - Channel 3 Frequency high | ||||
|     pub(crate) fn set_freq_hi(&mut self, byte: u8) { | ||||
|         self.freq_hi = byte.into(); | ||||
|  | ||||
|         if self.freq_hi.initial() { | ||||
|             // Length behaviour during trigger event | ||||
|             if self.length_timer == 0 { | ||||
|                 self.length_timer = 256; | ||||
|             } | ||||
|  | ||||
|             if self.dac_enabled { | ||||
|                 self.enabled = true; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn tick(&mut self) { | ||||
|         if self.freq_timer != 0 { | ||||
|             self.freq_timer -= 1; | ||||
|         } | ||||
|  | ||||
|         if self.freq_timer == 0 { | ||||
|             self.freq_timer = (2048 - self.frequency()) * 2; | ||||
|             self.offset = (self.offset + 1) % (WAVE_PATTERN_RAM_LEN * 2) as u8; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn amplitude(&self) -> f32 { | ||||
|         if self.dac_enabled { | ||||
|             let sample = self.read_sample(self.offset) >> self.volume.shift_count(); | ||||
|             let input = if self.enabled { sample } else { 0 }; | ||||
|  | ||||
|             (input as f32 / 7.5) - 1.0 | ||||
|         } else { | ||||
|             0.0 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn read_sample(&self, index: u8) -> u8 { | ||||
|         let i = (index / 2) as usize; | ||||
|  | ||||
|         if index % 2 == 0 { | ||||
|             // We grab the high nibble on even indexes | ||||
|             self.wave_ram[i] >> 4 | ||||
|         } else { | ||||
|             // We grab the low nibble on odd indexes | ||||
|             self.wave_ram[i] & 0x0F | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn frequency(&self) -> u16 { | ||||
|         (self.freq_hi.freq_bits() as u16) << 8 | self.freq_lo as u16 | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default)] | ||||
| pub(crate) struct Channel4 { | ||||
|     /// 0xFF20 | NR41 - Channel 4 Sound Length | ||||
|     len: u8, | ||||
|     /// 0xFF21 | NR42 - Channel 4 Volume Envelope | ||||
|     envelope: VolumeEnvelope, | ||||
|     /// 0xFF22 | NR43 - Chanel 4 Polynomial Counter | ||||
|     poly: PolynomialCounter, | ||||
|     /// 0xFF23 | NR44 - Channel 4 Counter / Consecutive Selector and Restart | ||||
|     freq: Ch4Frequency, | ||||
|  | ||||
|     // Envelope Functionality | ||||
|     period_timer: u8, | ||||
|     current_volume: u8, | ||||
|  | ||||
|     // Length Functionality | ||||
|     length_timer: u16, | ||||
|  | ||||
|     /// Linear Feedback Shift Register (15-bit) | ||||
|     lf_shift: u16, | ||||
|  | ||||
|     freq_timer: u16, | ||||
|  | ||||
|     enabled: bool, | ||||
| } | ||||
|  | ||||
| impl Channel4 { | ||||
|     /// 0xFF20 | NR41 - Channel 4 Sound Length | ||||
|     pub(crate) fn set_len(&mut self, byte: u8) { | ||||
|         self.len = byte & 0x3F; | ||||
|         self.length_timer = 64 - self.len as u16; | ||||
|     } | ||||
|  | ||||
|     /// 0xFF21 | NR42 - Channel 4 Volume Envelope | ||||
|     pub(crate) fn envelope(&self) -> u8 { | ||||
|         u8::from(self.envelope) | ||||
|     } | ||||
|  | ||||
|     /// 0xFF21 | NR42 - Channel 4 Volume Envelope | ||||
|     pub(crate) fn set_envelope(&mut self, byte: u8) { | ||||
|         self.envelope = byte.into(); | ||||
|  | ||||
|         if !self.is_dac_enabled() { | ||||
|             self.enabled = false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// 0xFF22 | NR43 - Chanel 4 Polynomial Counter | ||||
|     pub(crate) fn poly(&self) -> u8 { | ||||
|         u8::from(self.poly) | ||||
|     } | ||||
|  | ||||
|     /// 0xFF22 | NR43 - Chanel 4 Polynomial Counter | ||||
|     pub(crate) fn set_poly(&mut self, byte: u8) { | ||||
|         self.poly = byte.into(); | ||||
|     } | ||||
|  | ||||
|     /// 0xFF23 | NR44 - Channel 4 Counter / Consecutive Selector and Restart | ||||
|     pub(crate) fn frequency(&self) -> u8 { | ||||
|         u8::from(self.freq) | 0xBF | ||||
|     } | ||||
|  | ||||
|     /// 0xFF23 | NR44 - Channel 4 Counter / Consecutive Selector and Restart | ||||
|     pub(crate) fn set_frequency(&mut self, byte: u8) { | ||||
|         self.freq = byte.into(); | ||||
|  | ||||
|         if self.freq.initial() { | ||||
|             // Envelope behaviour during trigger event | ||||
|             self.period_timer = self.envelope.period(); | ||||
|             self.current_volume = self.envelope.init_vol(); | ||||
|  | ||||
|             // Length behaviour during trigger event | ||||
|             if self.length_timer == 0 { | ||||
|                 self.length_timer = 64; | ||||
|             } | ||||
|  | ||||
|             // LFSR behaviour during trigger event | ||||
|             self.lf_shift = 0x7FFF; | ||||
|  | ||||
|             if self.is_dac_enabled() { | ||||
|                 self.enabled = true; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn tick(&mut self) { | ||||
|         if self.freq_timer != 0 { | ||||
|             self.freq_timer -= 1; | ||||
|         } | ||||
|  | ||||
|         if self.freq_timer == 0 { | ||||
|             let divisor = Self::divisor(self.poly.divisor_code()) as u16; | ||||
|             self.freq_timer = divisor << self.poly.shift_count(); | ||||
|  | ||||
|             let xor_result = (self.lf_shift & 0x01) ^ ((self.lf_shift & 0x02) >> 1); | ||||
|             self.lf_shift = (self.lf_shift >> 1) | xor_result << 14; | ||||
|  | ||||
|             if let CounterWidth::Long = self.poly.counter_width() { | ||||
|                 self.lf_shift = (self.lf_shift & !(0x01 << 6)) | xor_result << 6; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn amplitude(&self) -> f32 { | ||||
|         if self.is_dac_enabled() { | ||||
|             let sample = (!self.lf_shift & 0x01) as u8 * self.current_volume; | ||||
|             let input = if self.enabled { sample } else { 0 }; | ||||
|  | ||||
|             (input as f32 / 7.5) - 1.0 | ||||
|         } else { | ||||
|             0.0 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn is_dac_enabled(&self) -> bool { | ||||
|         self.envelope.0 & 0xF8 != 0x00 | ||||
|     } | ||||
|  | ||||
|     fn divisor(code: u8) -> u8 { | ||||
|         if code == 0 { | ||||
|             return 8; | ||||
|         } | ||||
|  | ||||
|         code << 4 | ||||
|     } | ||||
| } | ||||
							
								
								
									
										101
									
								
								src/apu/gen.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										101
									
								
								src/apu/gen.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,101 @@ | ||||
| use rodio::Source; | ||||
| use rtrb::{Consumer, Producer, PushError, RingBuffer}; | ||||
|  | ||||
| pub(crate) const SAMPLE_RATE: u32 = 48000; // Hz | ||||
| const CHANNEL_COUNT: usize = 2; | ||||
| const BUFFER_CAPACITY: usize = 2048 * CHANNEL_COUNT; // # of samples * the # of channels | ||||
|  | ||||
| pub struct AudioSPSC<T> { | ||||
|     inner: RingBuffer<T>, | ||||
| } | ||||
|  | ||||
| impl<T> Default for AudioSPSC<T> { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             inner: RingBuffer::new(BUFFER_CAPACITY), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T> AudioSPSC<T> { | ||||
|     pub fn new(capacity: usize) -> Self { | ||||
|         Self { | ||||
|             inner: RingBuffer::new(capacity), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn init(self) -> (SampleProducer<T>, SampleConsumer<T>) { | ||||
|         let (prod, cons) = self.inner.split(); | ||||
|  | ||||
|         ( | ||||
|             SampleProducer { inner: prod }, | ||||
|             SampleConsumer { inner: cons }, | ||||
|         ) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct SampleProducer<T> { | ||||
|     inner: Producer<T>, | ||||
| } | ||||
|  | ||||
| impl<T> SampleProducer<T> { | ||||
|     pub(crate) fn push(&mut self, value: T) -> Result<(), PushError<T>> { | ||||
|         self.inner.push(value) | ||||
|     } | ||||
|  | ||||
|     #[allow(dead_code)] | ||||
|     pub(crate) fn available(&self) -> bool { | ||||
|         self.inner.slots() > 2 | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn available_blocking(&self) -> bool { | ||||
|         loop { | ||||
|             if self.inner.slots() > 2 { | ||||
|                 break true; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T> std::fmt::Debug for SampleProducer<T> { | ||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|         f.debug_struct(&format!("SampleProducer<{}>", std::any::type_name::<T>())) | ||||
|             .finish_non_exhaustive() | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct SampleConsumer<T> { | ||||
|     inner: Consumer<T>, | ||||
| } | ||||
|  | ||||
| impl Iterator for SampleConsumer<f32> { | ||||
|     type Item = f32; | ||||
|  | ||||
|     fn next(&mut self) -> Option<Self::Item> { | ||||
|         // As of 2021-07-28, PopError can only be Empty | ||||
|         Some(self.inner.pop().unwrap_or_default()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Source for SampleConsumer<f32> { | ||||
|     fn current_frame_len(&self) -> Option<usize> { | ||||
|         // A frame changes when the samples rate or | ||||
|         // number of channels change. This will never happen, so | ||||
|         // we return | ||||
|         None | ||||
|     } | ||||
|  | ||||
|     fn channels(&self) -> u16 { | ||||
|         // The Gameboy supports two channels | ||||
|         CHANNEL_COUNT as u16 | ||||
|     } | ||||
|  | ||||
|     fn sample_rate(&self) -> u32 { | ||||
|         SAMPLE_RATE | ||||
|     } | ||||
|  | ||||
|     fn total_duration(&self) -> Option<std::time::Duration> { | ||||
|         // The duration of this source is infinite | ||||
|         None | ||||
|     } | ||||
| } | ||||
							
								
								
									
										577
									
								
								src/apu/types.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										577
									
								
								src/apu/types.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,577 @@ | ||||
| use bitfield::bitfield; | ||||
|  | ||||
| pub(crate) mod ch1 { | ||||
|     use super::bitfield; | ||||
|  | ||||
|     bitfield! { | ||||
|         pub struct Sweep(u8); | ||||
|         impl Debug; | ||||
|         _period, _: 6, 4; | ||||
|         from into SweepDirection, _direction, _: 3, 3; | ||||
|         _shift_count, _: 2, 0; | ||||
|     } | ||||
|  | ||||
|     impl Sweep { | ||||
|         pub(crate) fn period(&self) -> u8 { | ||||
|             self._period() | ||||
|         } | ||||
|  | ||||
|         pub(crate) fn shift_count(&self) -> u8 { | ||||
|             self._shift_count() | ||||
|         } | ||||
|  | ||||
|         pub(crate) fn direction(&self) -> SweepDirection { | ||||
|             self._direction() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Copy for Sweep {} | ||||
|     impl Clone for Sweep { | ||||
|         fn clone(&self) -> Self { | ||||
|             *self | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Default for Sweep { | ||||
|         fn default() -> Self { | ||||
|             Self(0) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<u8> for Sweep { | ||||
|         fn from(byte: u8) -> Self { | ||||
|             Self(byte) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<Sweep> for u8 { | ||||
|         fn from(sweep: Sweep) -> Self { | ||||
|             sweep.0 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[derive(Debug, Clone, Copy)] | ||||
|     pub(crate) enum SweepDirection { | ||||
|         Increase = 0, | ||||
|         Decrease = 1, | ||||
|     } | ||||
|  | ||||
|     impl From<u8> for SweepDirection { | ||||
|         fn from(byte: u8) -> Self { | ||||
|             match byte & 0x01 { | ||||
|                 0b00 => Self::Increase, | ||||
|                 0b01 => Self::Decrease, | ||||
|                 _ => unreachable!("{:04X} is not a valid value for SweepChange", byte), | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<SweepDirection> for u8 { | ||||
|         fn from(change: SweepDirection) -> Self { | ||||
|             change as u8 | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub(super) mod ch3 { | ||||
|     #[derive(Debug, Clone, Copy)] | ||||
|     pub(crate) enum Volume { | ||||
|         Mute = 0, | ||||
|         Full = 1, | ||||
|         Half = 2, | ||||
|         Quarter = 3, | ||||
|     } | ||||
|  | ||||
|     impl Volume { | ||||
|         pub(crate) fn shift_count(&self) -> u8 { | ||||
|             use Volume::*; | ||||
|  | ||||
|             match *self { | ||||
|                 Mute => 4, | ||||
|                 Full => 0, | ||||
|                 Half => 1, | ||||
|                 Quarter => 2, | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Default for Volume { | ||||
|         fn default() -> Self { | ||||
|             Self::Mute | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub(super) mod ch4 { | ||||
|     use super::bitfield; | ||||
|  | ||||
|     bitfield! { | ||||
|         pub struct PolynomialCounter(u8); | ||||
|         impl Debug; | ||||
|         _shift_count, _: 7, 4; | ||||
|         from into CounterWidth, _counter_width, _: 3, 3; | ||||
|         _divisor_code, _: 2, 0; | ||||
|     } | ||||
|  | ||||
|     impl PolynomialCounter { | ||||
|         pub(crate) fn divisor_code(&self) -> u8 { | ||||
|             self._divisor_code() | ||||
|         } | ||||
|  | ||||
|         pub(crate) fn counter_width(&self) -> CounterWidth { | ||||
|             self._counter_width() | ||||
|         } | ||||
|  | ||||
|         pub(crate) fn shift_count(&self) -> u8 { | ||||
|             self._shift_count() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Copy for PolynomialCounter {} | ||||
|     impl Clone for PolynomialCounter { | ||||
|         fn clone(&self) -> Self { | ||||
|             *self | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Default for PolynomialCounter { | ||||
|         fn default() -> Self { | ||||
|             Self(0) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<u8> for PolynomialCounter { | ||||
|         fn from(byte: u8) -> Self { | ||||
|             Self(byte) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<PolynomialCounter> for u8 { | ||||
|         fn from(poly: PolynomialCounter) -> Self { | ||||
|             poly.0 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[derive(Debug, Clone, Copy)] | ||||
|     pub(crate) enum CounterWidth { | ||||
|         Long,  // 15 bits long | ||||
|         Short, // 7 bits long | ||||
|     } | ||||
|  | ||||
|     impl From<u8> for CounterWidth { | ||||
|         fn from(byte: u8) -> Self { | ||||
|             match byte & 0x01 { | ||||
|                 0b00 => Self::Short, | ||||
|                 0b01 => Self::Long, | ||||
|                 _ => unreachable!("{:#04X} is not a valid value for CounterWidth"), | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<CounterWidth> for u8 { | ||||
|         fn from(counter_width: CounterWidth) -> Self { | ||||
|             counter_width as u8 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     bitfield! { | ||||
|         pub struct Frequency(u8); | ||||
|         impl Debug; | ||||
|         _initial, _: 7; | ||||
|         _length_disable, _: 6; | ||||
|     } | ||||
|  | ||||
|     impl Frequency { | ||||
|         pub(crate) fn length_disable(&self) -> bool { | ||||
|             self._length_disable() | ||||
|         } | ||||
|  | ||||
|         pub(crate) fn initial(&self) -> bool { | ||||
|             self._initial() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Copy for Frequency {} | ||||
|     impl Clone for Frequency { | ||||
|         fn clone(&self) -> Self { | ||||
|             *self | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Default for Frequency { | ||||
|         fn default() -> Self { | ||||
|             Self(0) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<u8> for Frequency { | ||||
|         fn from(byte: u8) -> Self { | ||||
|             Self(byte & 0xC0) // Only bits 7 and 6 hold anything of value | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<Frequency> for u8 { | ||||
|         fn from(state: Frequency) -> Self { | ||||
|             state.0 & 0x40 // Only bit 6 holds anything of value | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub(super) mod common { | ||||
|     use super::bitfield; | ||||
|  | ||||
|     bitfield! { | ||||
|         pub struct FrequencyHigh(u8); | ||||
|         impl Debug; | ||||
|         _initial, _: 7; | ||||
|         _length_disable, _: 6; | ||||
|         pub freq_bits, set_freq_bits: 2, 0; | ||||
|     } | ||||
|  | ||||
|     impl FrequencyHigh { | ||||
|         pub(crate) fn initial(&self) -> bool { | ||||
|             self._initial() | ||||
|         } | ||||
|  | ||||
|         pub(crate) fn length_disable(&self) -> bool { | ||||
|             self._length_disable() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Copy for FrequencyHigh {} | ||||
|     impl Clone for FrequencyHigh { | ||||
|         fn clone(&self) -> Self { | ||||
|             *self | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Default for FrequencyHigh { | ||||
|         fn default() -> Self { | ||||
|             Self(0) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<u8> for FrequencyHigh { | ||||
|         fn from(byte: u8) -> Self { | ||||
|             Self(byte) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<FrequencyHigh> for u8 { | ||||
|         fn from(freq: FrequencyHigh) -> Self { | ||||
|             freq.0 & 0x40 // Only bit 6 can be read | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     bitfield! { | ||||
|         pub struct VolumeEnvelope(u8); | ||||
|         impl Debug; | ||||
|         _init_vol, _: 7, 4; | ||||
|         from into EnvelopeDirection, _direction, _: 3, 3; | ||||
|         _period, _: 2, 0; | ||||
|     } | ||||
|  | ||||
|     impl VolumeEnvelope { | ||||
|         pub(crate) fn init_vol(&self) -> u8 { | ||||
|             self._init_vol() | ||||
|         } | ||||
|  | ||||
|         pub(crate) fn direction(&self) -> EnvelopeDirection { | ||||
|             self._direction() | ||||
|         } | ||||
|  | ||||
|         pub(crate) fn period(&self) -> u8 { | ||||
|             self._period() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Copy for VolumeEnvelope {} | ||||
|     impl Clone for VolumeEnvelope { | ||||
|         fn clone(&self) -> Self { | ||||
|             *self | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Default for VolumeEnvelope { | ||||
|         fn default() -> Self { | ||||
|             Self(0) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<u8> for VolumeEnvelope { | ||||
|         fn from(byte: u8) -> Self { | ||||
|             Self(byte) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<VolumeEnvelope> for u8 { | ||||
|         fn from(envelope: VolumeEnvelope) -> Self { | ||||
|             envelope.0 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[derive(Debug, Clone, Copy)] | ||||
|     pub(crate) enum EnvelopeDirection { | ||||
|         Decrease = 0, | ||||
|         Increase = 1, | ||||
|     } | ||||
|  | ||||
|     impl From<u8> for EnvelopeDirection { | ||||
|         fn from(byte: u8) -> Self { | ||||
|             match byte & 0b01 { | ||||
|                 0b00 => Self::Decrease, | ||||
|                 0b01 => Self::Increase, | ||||
|                 _ => unreachable!("{:#04X} is not a valid value for EnvelopeDirection", byte), | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<EnvelopeDirection> for u8 { | ||||
|         fn from(direction: EnvelopeDirection) -> Self { | ||||
|             direction as u8 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Default for EnvelopeDirection { | ||||
|         fn default() -> Self { | ||||
|             Self::Decrease | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     bitfield! { | ||||
|         pub struct SoundDuty(u8); | ||||
|         impl Debug; | ||||
|         from into WavePattern, _wave_pattern, _: 7, 6; | ||||
|         _sound_length, _: 5, 0; | ||||
|     } | ||||
|  | ||||
|     impl SoundDuty { | ||||
|         pub(crate) fn wave_pattern(&self) -> WavePattern { | ||||
|             self._wave_pattern() | ||||
|         } | ||||
|  | ||||
|         pub(crate) fn sound_length(&self) -> u8 { | ||||
|             self._sound_length() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Copy for SoundDuty {} | ||||
|     impl Clone for SoundDuty { | ||||
|         fn clone(&self) -> Self { | ||||
|             *self | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Default for SoundDuty { | ||||
|         fn default() -> Self { | ||||
|             Self(0) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<u8> for SoundDuty { | ||||
|         fn from(byte: u8) -> Self { | ||||
|             Self(byte) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<SoundDuty> for u8 { | ||||
|         fn from(duty: SoundDuty) -> Self { | ||||
|             duty.0 & 0xC0 // Only bits 7 and 6 can be read | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[derive(Debug, Clone, Copy)] | ||||
|     pub(crate) enum WavePattern { | ||||
|         OneEighth = 0,     // 12.5% ( _-------_-------_------- ) | ||||
|         OneQuarter = 1,    // 25%   ( __------__------__------ ) | ||||
|         OneHalf = 2,       // 50%   ( ____----____----____---- ) (normal) | ||||
|         ThreeQuarters = 3, // 75%   ( ______--______--______-- ) | ||||
|     } | ||||
|  | ||||
|     impl WavePattern { | ||||
|         pub(crate) const fn amplitude(&self, index: u8) -> u8 { | ||||
|             use WavePattern::*; | ||||
|             let i = 7 - index; // an index of 0 should get the highest bit | ||||
|  | ||||
|             match *self { | ||||
|                 OneEighth => (0b00000001 >> i) & 0x01, | ||||
|                 OneQuarter => (0b10000001 >> i) & 0x01, | ||||
|                 OneHalf => (0b10000111 >> i) & 0x01, | ||||
|                 ThreeQuarters => (0b01111110 >> i) & 0x01, | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl Default for WavePattern { | ||||
|         fn default() -> Self { | ||||
|             Self::OneEighth // Rationale: OneEighth is 0x00 | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<WavePattern> for u8 { | ||||
|         fn from(pattern: WavePattern) -> Self { | ||||
|             pattern as Self | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl From<u8> for WavePattern { | ||||
|         fn from(byte: u8) -> Self { | ||||
|             match byte & 0b11 { | ||||
|                 0b00 => Self::OneEighth, | ||||
|                 0b01 => Self::OneQuarter, | ||||
|                 0b10 => Self::OneHalf, | ||||
|                 0b11 => Self::ThreeQuarters, | ||||
|                 _ => unreachable!("{:#04X} is not a valid value for WavePattern", byte), | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| bitfield! { | ||||
|     pub struct SoundOutput(u8); | ||||
|     impl Debug; | ||||
|     pub ch4_left, _: 7; | ||||
|     pub ch3_left, _: 6; | ||||
|     pub ch2_left, _: 5; | ||||
|     pub ch1_left, _: 4; | ||||
|     pub ch4_right, _: 3; | ||||
|     pub ch3_right, _: 2; | ||||
|     pub ch2_right, _: 1; | ||||
|     pub ch1_right, _: 0; | ||||
| } | ||||
|  | ||||
| impl SoundOutput { | ||||
|     pub(super) fn ch1(&self) -> (bool, bool) { | ||||
|         (self.ch1_left(), self.ch1_right()) | ||||
|     } | ||||
|  | ||||
|     pub(super) fn ch2(&self) -> (bool, bool) { | ||||
|         (self.ch2_left(), self.ch2_right()) | ||||
|     } | ||||
|  | ||||
|     pub(super) fn ch3(&self) -> (bool, bool) { | ||||
|         (self.ch3_left(), self.ch3_right()) | ||||
|     } | ||||
|  | ||||
|     pub(super) fn ch4(&self) -> (bool, bool) { | ||||
|         (self.ch4_left(), self.ch4_right()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Copy for SoundOutput {} | ||||
| impl Clone for SoundOutput { | ||||
|     fn clone(&self) -> Self { | ||||
|         *self | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for SoundOutput { | ||||
|     fn default() -> Self { | ||||
|         Self(0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for SoundOutput { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         Self(byte) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<SoundOutput> for u8 { | ||||
|     fn from(output: SoundOutput) -> Self { | ||||
|         output.0 | ||||
|     } | ||||
| } | ||||
|  | ||||
| bitfield! { | ||||
|     pub struct ChannelControl(u8); | ||||
|     impl Debug; | ||||
|     vin_left, _: 7; | ||||
|     _left_volume, _: 6, 4; | ||||
|     vin_right, _: 3; | ||||
|     _right_volume, _: 2, 0; | ||||
| } | ||||
|  | ||||
| impl ChannelControl { | ||||
|     pub(crate) fn left_volume(&self) -> f32 { | ||||
|         self._left_volume() as f32 | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn right_volume(&self) -> f32 { | ||||
|         self._right_volume() as f32 | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Copy for ChannelControl {} | ||||
| impl Clone for ChannelControl { | ||||
|     fn clone(&self) -> Self { | ||||
|         *self | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for ChannelControl { | ||||
|     fn default() -> Self { | ||||
|         Self(0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for ChannelControl { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         Self(byte) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<ChannelControl> for u8 { | ||||
|     fn from(ctrl: ChannelControl) -> Self { | ||||
|         ctrl.0 | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub(super) mod fs { | ||||
|     #[derive(Debug)] | ||||
|     pub(crate) struct FrameSequencer { | ||||
|         step: u8, | ||||
|         state: State, | ||||
|     } | ||||
|  | ||||
|     impl Default for FrameSequencer { | ||||
|         fn default() -> Self { | ||||
|             Self { | ||||
|                 step: Default::default(), | ||||
|                 state: State::Length, | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl FrameSequencer { | ||||
|         pub(crate) fn next(&mut self) { | ||||
|             use State::*; | ||||
|  | ||||
|             self.step = (self.step + 1) % 8; | ||||
|             self.state = match self.step { | ||||
|                 1 | 3 | 5 => Nothing, | ||||
|                 0 | 4 => Length, | ||||
|                 2 | 6 => LengthAndSweep, | ||||
|                 7 => Envelope, | ||||
|                 _ => unreachable!("Step {} is invalid for the Frame Sequencer", self.step), | ||||
|             }; | ||||
|         } | ||||
|  | ||||
|         pub(crate) fn state(&self) -> State { | ||||
|             self.state | ||||
|         } | ||||
|  | ||||
|         pub(crate) fn reset(&mut self) { | ||||
|             self.step = Default::default(); | ||||
|             self.state = State::Length; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[derive(Debug, Clone, Copy)] | ||||
|     pub(crate) enum State { | ||||
|         Length, | ||||
|         Nothing, | ||||
|         LengthAndSweep, | ||||
|         Envelope, | ||||
|     } | ||||
| } | ||||
							
								
								
									
										149
									
								
								src/bus.rs
									
									
									
									
									
								
							
							
						
						
									
										149
									
								
								src/bus.rs
									
									
									
									
									
								
							| @@ -1,26 +1,25 @@ | ||||
| use crate::apu::Apu; | ||||
| use crate::cartridge::Cartridge; | ||||
| use crate::high_ram::HighRam; | ||||
| use crate::interrupt::{Interrupt, InterruptFlag}; | ||||
| use crate::joypad::Joypad; | ||||
| use crate::ppu::{Ppu, PpuMode}; | ||||
| use crate::serial::Serial; | ||||
| use crate::sound::{AudioSender, Sound}; | ||||
| use crate::timer::Timer; | ||||
| use crate::work_ram::{VariableWorkRam, WorkRam}; | ||||
| use std::{fs::File, io::Read}; | ||||
|  | ||||
| const BOOT_ROM_SIZE: usize = 0x100; | ||||
| pub(crate) const BOOT_SIZE: usize = 0x100; | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| #[derive(Debug)] | ||||
| pub struct Bus { | ||||
|     boot: Option<[u8; BOOT_ROM_SIZE]>, // Boot ROM is 256b long | ||||
|     cartridge: Option<Cartridge>, | ||||
|     pub ppu: Ppu, | ||||
|     boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long | ||||
|     cart: Option<Cartridge>, | ||||
|     pub(crate) ppu: Ppu, | ||||
|     work_ram: WorkRam, | ||||
|     var_ram: VariableWorkRam, | ||||
|     pub(crate) timer: Timer, | ||||
|     int: Interrupt, | ||||
|     snd: Sound, | ||||
|     pub(crate) apu: Apu, | ||||
|     high_ram: HighRam, | ||||
|     serial: Serial, | ||||
|     pub(crate) joypad: Joypad, | ||||
| @@ -30,13 +29,13 @@ impl Default for Bus { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             boot: None, | ||||
|             cartridge: None, | ||||
|             cart: None, | ||||
|             ppu: Default::default(), | ||||
|             work_ram: Default::default(), | ||||
|             var_ram: Default::default(), | ||||
|             timer: Default::default(), | ||||
|             int: Default::default(), | ||||
|             snd: Default::default(), | ||||
|             apu: Default::default(), | ||||
|             high_ram: Default::default(), | ||||
|             serial: Default::default(), | ||||
|             joypad: Default::default(), | ||||
| @@ -45,44 +44,41 @@ impl Default for Bus { | ||||
| } | ||||
|  | ||||
| impl Bus { | ||||
|     pub(crate) fn with_boot(path: &str) -> anyhow::Result<Self> { | ||||
|         let mut file = File::open(path)?; | ||||
|         let mut boot_rom = [0u8; 256]; | ||||
|  | ||||
|         file.read_exact(&mut boot_rom)?; | ||||
|  | ||||
|         Ok(Self { | ||||
|             boot: Some(boot_rom), | ||||
|     pub(crate) fn with_boot(rom: [u8; 256]) -> Self { | ||||
|         Self { | ||||
|             boot: Some(rom), | ||||
|             ..Default::default() | ||||
|         }) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> { | ||||
|         self.cartridge = Some(Cartridge::new(path)?); | ||||
|         Ok(()) | ||||
|     pub(crate) fn load_cart(&mut self, rom: Vec<u8>) { | ||||
|         self.cart = Some(Cartridge::new(rom)); | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn rom_title(&self) -> Option<&str> { | ||||
|         self.cartridge.as_ref()?.title() | ||||
|     pub(crate) fn cart_title(&self) -> Option<&str> { | ||||
|         self.cart.as_ref()?.title() | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn pass_audio_src(&mut self, sender: AudioSender) { | ||||
|         self.snd.set_audio_src(sender) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn is_mpsc_still_full(&mut self) -> bool { | ||||
|         self.snd.is_mpsc_still_full() | ||||
|     #[allow(dead_code)] | ||||
|     pub(crate) fn boot_mapped(&self) -> bool { | ||||
|         self.boot.is_some() | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn clock(&mut self) { | ||||
|         self.ppu.clock(); | ||||
|         self.timer.clock(); | ||||
|         self.snd.clock(self.timer.divider); | ||||
|         self.clock_dma(); | ||||
|         self.tick(4); | ||||
|     } | ||||
|  | ||||
|     fn clock_dma(&mut self) { | ||||
|         if let Some((src_addr, dest_addr)) = self.ppu.dma.clock() { | ||||
|     fn tick(&mut self, limit: u8) { | ||||
|         for _ in 0..limit { | ||||
|             self.timer.tick(); | ||||
|             self.ppu.tick(); | ||||
|             self.apu.tick(self.timer.divider); | ||||
|             self.dma_tick() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn dma_tick(&mut self) { | ||||
|         if let Some((src_addr, dest_addr)) = self.ppu.dma.tick() { | ||||
|             let byte = self.oam_read_byte(src_addr); | ||||
|             self.oam_write_byte(dest_addr, byte); | ||||
|         } | ||||
| @@ -101,13 +97,13 @@ impl Bus { | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 match self.cartridge.as_ref() { | ||||
|                 match self.cart.as_ref() { | ||||
|                     Some(cart) => cart.read_byte(addr), | ||||
|                     None => panic!("Tried to read from a non-existent cartridge"), | ||||
|                 } | ||||
|             } | ||||
|             0x8000..=0x9FFF => self.ppu.read_byte(addr), // 8KB Video RAM | ||||
|             0xA000..=0xBFFF => match self.cartridge.as_ref() { | ||||
|             0xA000..=0xBFFF => match self.cart.as_ref() { | ||||
|                 // 8KB External RAM | ||||
|                 Some(cart) => cart.read_byte(addr), | ||||
|                 None => panic!("Tried to read from a non-existent cartridge"), | ||||
| @@ -154,7 +150,7 @@ impl BusIo for Bus { | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
|                 match self.cartridge.as_ref() { | ||||
|                 match self.cart.as_ref() { | ||||
|                     Some(cart) => cart.read_byte(addr), | ||||
|                     None => panic!("Tried to read from a non-existent cartridge"), | ||||
|                 } | ||||
| @@ -166,7 +162,7 @@ impl BusIo for Bus { | ||||
|                     _ => self.ppu.read_byte(addr), | ||||
|                 } | ||||
|             } | ||||
|             0xA000..=0xBFFF => match self.cartridge.as_ref() { | ||||
|             0xA000..=0xBFFF => match self.cart.as_ref() { | ||||
|                 // 8KB External RAM | ||||
|                 Some(cart) => cart.read_byte(addr), | ||||
|                 None => panic!("Tried to read from a non-existent cartridge"), | ||||
| @@ -220,29 +216,11 @@ impl BusIo for Bus { | ||||
|                     0x01 => self.serial.next, | ||||
|                     0x02 => self.serial.ctrl.into(), | ||||
|                     0x04 => (self.timer.divider >> 8) as u8, | ||||
|                     0x05 => self.timer.counter, | ||||
|                     0x05 => self.timer.tima(), | ||||
|                     0x06 => self.timer.modulo, | ||||
|                     0x07 => self.timer.ctrl.into(), | ||||
|                     0x0F => self.interrupt_flag().into(), | ||||
|                     0x10 => self.snd.ch1.sweep.into(), | ||||
|                     0x11 => self.snd.ch1.duty(), | ||||
|                     0x12 => self.snd.ch1.envelope.into(), | ||||
|                     0x14 => self.snd.ch1.freq_hi(), | ||||
|                     0x16 => self.snd.ch2.duty(), | ||||
|                     0x17 => self.snd.ch2.envelope.into(), | ||||
|                     0x19 => self.snd.ch2.freq_hi(), | ||||
|                     0x1A => self.snd.ch3.enabled(), | ||||
|                     0x1B => self.snd.ch3.len(), | ||||
|                     0x1C => self.snd.ch3.volume(), | ||||
|                     0x1E => self.snd.ch3.freq_hi(), | ||||
|                     0x20 => self.snd.ch4.len(), | ||||
|                     0x21 => self.snd.ch4.envelope.into(), | ||||
|                     0x22 => self.snd.ch4.poly.into(), | ||||
|                     0x23 => self.snd.ch4.freq_data(), | ||||
|                     0x24 => self.snd.ctrl.channel.into(), | ||||
|                     0x25 => self.snd.ctrl.output.into(), | ||||
|                     0x26 => self.snd.ctrl.status(&self.snd), | ||||
|                     0x30..=0x3F => self.snd.ch3.wave_ram[addr as usize - 0xFF30], | ||||
|                     0x10..=0x3F => self.apu.read_byte(addr), | ||||
|                     0x40 => self.ppu.ctrl.into(), | ||||
|                     0x41 => self.ppu.stat.into(), | ||||
|                     0x42 => self.ppu.pos.scroll_y, | ||||
| @@ -255,8 +233,10 @@ impl BusIo for Bus { | ||||
|                     0x49 => self.ppu.monochrome.obj_palette_1.into(), | ||||
|                     0x4A => self.ppu.pos.window_y, | ||||
|                     0x4B => self.ppu.pos.window_x, | ||||
|                     0x4D => 0xFF, // CGB Specific Register | ||||
|                     _ => unimplemented!("Unable to read {:#06X} in I/O Registers", addr), | ||||
|                     _ => { | ||||
|                         eprintln!("Read 0xFF from unused IO register {:#06X}.", addr); | ||||
|                         0xFF | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             0xFF80..=0xFFFE => { | ||||
| @@ -275,7 +255,7 @@ impl BusIo for Bus { | ||||
|             0x0000..=0x7FFF => { | ||||
|                 // 16KB ROM bank 00 (ends at 0x3FFF) | ||||
|                 // and 16KB ROM Bank 01 -> NN (switchable via MB) | ||||
|                 match self.cartridge.as_mut() { | ||||
|                 match self.cart.as_mut() { | ||||
|                     Some(cart) => cart.write_byte(addr, byte), | ||||
|                     None => panic!("Tried to write into non-existent cartridge"), | ||||
|                 } | ||||
| @@ -289,7 +269,7 @@ impl BusIo for Bus { | ||||
|             } | ||||
|             0xA000..=0xBFFF => { | ||||
|                 // 8KB External RAM | ||||
|                 match self.cartridge.as_mut() { | ||||
|                 match self.cart.as_mut() { | ||||
|                     Some(cart) => cart.write_byte(addr, byte), | ||||
|                     None => panic!("Tried to write into non-existent cartridge"), | ||||
|                 } | ||||
| @@ -324,7 +304,7 @@ impl BusIo for Bus { | ||||
|                     _ => {} | ||||
|                 } | ||||
|             } | ||||
|             0xFEA0..=0xFEFF => {} // TODO: As far as I know, writes to here do nothing. | ||||
|             0xFEA0..=0xFEFF => {} // FIXME: As far as I know, writes to here do nothing. | ||||
|             0xFF00..=0xFF7F => { | ||||
|                 // IO Registers | ||||
|  | ||||
| @@ -335,32 +315,11 @@ impl BusIo for Bus { | ||||
|                     0x01 => self.serial.next = byte, | ||||
|                     0x02 => self.serial.ctrl = byte.into(), | ||||
|                     0x04 => self.timer.divider = 0x0000, | ||||
|                     0x05 => self.timer.counter = byte, | ||||
|                     0x05 => self.timer.set_tima(byte), | ||||
|                     0x06 => self.timer.modulo = byte, | ||||
|                     0x07 => self.timer.ctrl = byte.into(), | ||||
|                     0x0F => self.set_interrupt_flag(byte), | ||||
|                     0x10 => self.snd.ch1.sweep = byte.into(), | ||||
|                     0x11 => self.snd.ch1.set_duty(byte), | ||||
|                     0x12 => self.snd.ch1.envelope = byte.into(), | ||||
|                     0x13 => self.snd.ch1.set_freq_lo(byte), | ||||
|                     0x14 => self.snd.ch1.set_freq_hi(byte), | ||||
|                     0x16 => self.snd.ch2.set_duty(byte), | ||||
|                     0x17 => self.snd.ch2.envelope = byte.into(), | ||||
|                     0x18 => self.snd.ch2.set_freq_lo(byte), | ||||
|                     0x19 => self.snd.ch2.set_freq_hi(byte), | ||||
|                     0x1A => self.snd.ch3.set_enabled(byte), | ||||
|                     0x1B => self.snd.ch3.set_len(byte), | ||||
|                     0x1C => self.snd.ch3.set_volume(byte), | ||||
|                     0x1D => self.snd.ch3.set_freq_lo(byte), | ||||
|                     0x1E => self.snd.ch3.set_freq_hi(byte), | ||||
|                     0x20 => self.snd.ch4.set_len(byte), | ||||
|                     0x21 => self.snd.ch4.envelope = byte.into(), | ||||
|                     0x22 => self.snd.ch4.poly = byte.into(), | ||||
|                     0x23 => self.snd.ch4.set_freq_data(byte), | ||||
|                     0x24 => self.snd.ctrl.channel = byte.into(), | ||||
|                     0x25 => self.snd.ctrl.output = byte.into(), | ||||
|                     0x26 => self.snd.ctrl.set_status(byte), // FIXME: Should we control which bytes are written to here? | ||||
|                     0x30..=0x3F => self.snd.ch3.wave_ram[addr as usize - 0xFF30] = byte, | ||||
|                     0x10..=0x3F => self.apu.write_byte(addr, byte), | ||||
|                     0x40 => self.ppu.ctrl = byte.into(), | ||||
|                     0x41 => self.ppu.stat.update(byte), | ||||
|                     0x42 => self.ppu.pos.scroll_y = byte, | ||||
| @@ -392,8 +351,7 @@ impl BusIo for Bus { | ||||
|                             self.boot = None; | ||||
|                         } | ||||
|                     } | ||||
|                     0x7F => {} // Tetris tries to write to this non-existent IO Address | ||||
|                     _ => unimplemented!("Unable to write to {:#06X} in I/O Registers", addr), | ||||
|                     _ => eprintln!("Wrote {:#04X} to unused IO register {:#06X}.", byte, addr), | ||||
|                 }; | ||||
|             } | ||||
|             0xFF80..=0xFFFE => { | ||||
| @@ -408,17 +366,6 @@ impl BusIo for Bus { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Bus { | ||||
|     pub(crate) fn read_word(&self, addr: u16) -> u16 { | ||||
|         (self.read_byte(addr + 1) as u16) << 8 | self.read_byte(addr) as u16 | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn write_word(&mut self, addr: u16, word: u16) { | ||||
|         self.write_byte(addr + 1, (word >> 8) as u8); | ||||
|         self.write_byte(addr, (word & 0x00FF) as u8); | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Bus { | ||||
|     fn interrupt_flag(&self) -> InterruptFlag { | ||||
|         // Read the current interrupt information from the PPU | ||||
|   | ||||
							
								
								
									
										565
									
								
								src/cartridge.rs
									
									
									
									
									
								
							
							
						
						
									
										565
									
								
								src/cartridge.rs
									
									
									
									
									
								
							| @@ -1,137 +1,99 @@ | ||||
| use std::fs::File; | ||||
| use std::io::{self, Read}; | ||||
| use std::path::Path; | ||||
|  | ||||
| use crate::bus::BusIo; | ||||
|  | ||||
| const RAM_SIZE_ADDRESS: usize = 0x0149; | ||||
| const ROM_SIZE_ADDRESS: usize = 0x0148; | ||||
| const MBC_TYPE_ADDRESS: usize = 0x0147; | ||||
| const ROM_TITLE_RANGE: std::ops::RangeInclusive<usize> = 0x0134..=0x0143; | ||||
|  | ||||
| #[derive(Debug, Clone, Default)] | ||||
| #[derive(Debug, Default)] | ||||
| pub(crate) struct Cartridge { | ||||
|     memory: Vec<u8>, | ||||
|     title: Option<String>, | ||||
|     mbc: Box<dyn MemoryBankController>, | ||||
|     mbc: Box<dyn MBCIo>, | ||||
| } | ||||
|  | ||||
| impl Cartridge { | ||||
|     pub(crate) fn new<P: AsRef<Path> + ?Sized>(path: &P) -> io::Result<Self> { | ||||
|         let mut memory = vec![]; | ||||
|         let mut rom = File::open(path)?; | ||||
|         rom.read_to_end(&mut memory)?; | ||||
|  | ||||
|     pub(crate) fn new(memory: Vec<u8>) -> Self { | ||||
|         let title = Self::find_title(&memory); | ||||
|         eprintln!("Cartridge Title: {:?}", title); | ||||
|  | ||||
|         Ok(Self { | ||||
|         Self { | ||||
|             mbc: Self::detect_mbc(&memory), | ||||
|             title, | ||||
|             memory, | ||||
|         }) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn detect_mbc(memory: &[u8]) -> Box<dyn MemoryBankController> { | ||||
|         let ram_size = Self::find_ram_size(memory); | ||||
|         let bank_count = Self::find_bank_count(memory); | ||||
|     fn detect_mbc(memory: &[u8]) -> Box<dyn MBCIo> { | ||||
|         let ram_size = Self::detect_ram_info(memory); | ||||
|         let rom_size = Self::detect_rom_info(memory); | ||||
|         let mbc_kind = Self::find_mbc(memory); | ||||
|         let ram_byte_count = ram_size.len(); | ||||
|         let ram_cap = ram_size.capacity(); | ||||
|         let rom_cap = rom_size.capacity(); | ||||
|  | ||||
|         eprintln!("Cartridge Ram Size: {} bytes", ram_size.len()); | ||||
|         eprintln!("Cartridge ROM Size: {} bytes", bank_count.size()); | ||||
|         eprintln!("Cartridge Ram Size: {} bytes", ram_cap); | ||||
|         eprintln!("Cartridge ROM Size: {} bytes", rom_size.capacity()); | ||||
|         eprintln!("MBC Type: {:?}", mbc_kind); | ||||
|  | ||||
|         match mbc_kind { | ||||
|             MbcKind::None => Box::new(NoMbc {}), | ||||
|             MbcKind::Mbc1 => { | ||||
|                 let mbc = Mbc1 { | ||||
|                     ram_size, | ||||
|                     ram: vec![0; ram_byte_count as usize], | ||||
|                     bank_count, | ||||
|                     ..Default::default() | ||||
|                 }; | ||||
|  | ||||
|                 Box::new(mbc) | ||||
|             } | ||||
|             MbcKind::Mbc1WithBattery => { | ||||
|                 // TODO: Implement Saving | ||||
|                 let mbc = Mbc1 { | ||||
|                     ram_size, | ||||
|                     ram: vec![0; ram_byte_count as usize], | ||||
|                     bank_count, | ||||
|                     ..Default::default() | ||||
|                 }; | ||||
|  | ||||
|                 Box::new(mbc) | ||||
|             } | ||||
|             MbcKind::Mbc3WithBattery => { | ||||
|                 // TODO: Implement Saving | ||||
|                 let mbc = MBC3 { | ||||
|                     ram_size, | ||||
|                     ram: vec![0; ram_byte_count as usize], | ||||
|                     ..Default::default() | ||||
|                 }; | ||||
|  | ||||
|                 Box::new(mbc) | ||||
|             } | ||||
|             MbcKind::Mbc3 => { | ||||
|                 let mbc = MBC3 { | ||||
|                     ram_size, | ||||
|                     ram: vec![0; ram_byte_count as usize], | ||||
|                     ..Default::default() | ||||
|                 }; | ||||
|  | ||||
|                 Box::new(mbc) | ||||
|             } | ||||
|             MbcKind::Mbc5 => todo!("Implement MBC5"), | ||||
|             MBCKind::None => Box::new(NoMBC), | ||||
|             MBCKind::MBC1 => Box::new(MBC1::new(ram_size, rom_size)), | ||||
|             MBCKind::MBC1WithBattery => Box::new(MBC1::new(ram_size, rom_size)), // TODO: Implement Saving | ||||
|             MBCKind::MBC2 => Box::new(MBC2::new(rom_cap)), | ||||
|             MBCKind::MBC2WithBattery => Box::new(MBC2::new(rom_cap)), // TODO: Implement Saving | ||||
|             MBCKind::MBC3 => Box::new(MBC3::new(ram_cap)), | ||||
|             MBCKind::MBC3WithBattery => Box::new(MBC3::new(ram_cap)), // TODO: Implement Saving | ||||
|             MBCKind::MBC5 => Box::new(MBC5::new(ram_cap, rom_cap)), | ||||
|             MBCKind::MBC5WithBattery => Box::new(MBC5::new(ram_cap, rom_cap)), // TDO: Implement Saving | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn find_title(memory: &[u8]) -> Option<String> { | ||||
|         // FIXME: Get rid of magic values and handle cases | ||||
|         // where 0x134..0x143 reads past the length of the | ||||
|         // string | ||||
|         let slice = &memory[ROM_TITLE_RANGE]; | ||||
|         let with_nulls = std::str::from_utf8(slice).ok(); | ||||
|         let trimmed = with_nulls.map(|s| s.trim_matches('\0').trim()); | ||||
|  | ||||
|         let slice = &memory[0x134..0x143]; | ||||
|  | ||||
|         let str_with_nulls = std::str::from_utf8(slice).ok(); | ||||
|         str_with_nulls.map(|s| s.trim_matches('\0').to_string()) | ||||
|         match trimmed { | ||||
|             Some("") | None => None, | ||||
|             Some(_) => trimmed.map(String::from), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn title(&self) -> Option<&str> { | ||||
|         self.title.as_deref() | ||||
|     } | ||||
|  | ||||
|     fn find_ram_size(memory: &[u8]) -> RamSize { | ||||
|     fn detect_ram_info(memory: &[u8]) -> RamSize { | ||||
|         let id = memory[RAM_SIZE_ADDRESS]; | ||||
|         id.into() | ||||
|     } | ||||
|  | ||||
|     fn find_bank_count(memory: &[u8]) -> BankCount { | ||||
|     fn detect_rom_info(memory: &[u8]) -> RomSize { | ||||
|         let id = memory[ROM_SIZE_ADDRESS]; | ||||
|         id.into() | ||||
|     } | ||||
|  | ||||
|     fn find_mbc(memory: &[u8]) -> MbcKind { | ||||
|         let id = memory[MBC_TYPE_ADDRESS]; | ||||
|     fn find_mbc(memory: &[u8]) -> MBCKind { | ||||
|         use MBCKind::*; | ||||
|  | ||||
|         // TODO: Refactor this to match the other enums in this module | ||||
|         match id { | ||||
|             0x00 => MbcKind::None, | ||||
|             0x01 => MbcKind::Mbc1, | ||||
|             0x02 => MbcKind::Mbc1, | ||||
|             0x03 => MbcKind::Mbc1WithBattery, | ||||
|             0x19 => MbcKind::Mbc5, | ||||
|             0x13 => MbcKind::Mbc3WithBattery, | ||||
|             0x11 => MbcKind::Mbc3, | ||||
|             _ => unimplemented!("id {:#04X} is an unsupported MBC", id), | ||||
|         match memory[MBC_TYPE_ADDRESS] { | ||||
|             0x00 => None, | ||||
|             0x01 | 0x02 => MBC1, | ||||
|             0x03 => MBC1WithBattery, | ||||
|             0x05 => MBC2, | ||||
|             0x06 => MBC2WithBattery, | ||||
|             0x19 | 0x1A => MBC5, | ||||
|             0x1B => MBC5WithBattery, | ||||
|             0x13 => MBC3WithBattery, | ||||
|             0x11 | 0x12 => MBC3, | ||||
|             id => unimplemented!("id {:#04X} is an unsupported MBC", id), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl BusIo for Cartridge { | ||||
|     fn read_byte(&self, addr: u16) -> u8 { | ||||
|         use MbcResult::*; | ||||
|         use MBCResult::*; | ||||
|  | ||||
|         match self.mbc.handle_read(addr) { | ||||
|             Address(addr) => self.memory[addr], | ||||
| @@ -144,92 +106,90 @@ impl BusIo for Cartridge { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| struct Mbc1 { | ||||
| #[derive(Debug)] | ||||
| struct MBC1 { | ||||
|     /// 5-bit number | ||||
|     rom_bank: u8, | ||||
|     /// 2-bit number | ||||
|     ram_bank: u8, | ||||
|     mode: bool, | ||||
|     ram_size: RamSize, | ||||
|     ram: Vec<u8>, | ||||
|     bank_count: BankCount, | ||||
|     ram_enabled: bool, | ||||
|     memory: Vec<u8>, | ||||
|     rom_size: RomSize, | ||||
|     mem_enabled: bool, | ||||
| } | ||||
|  | ||||
| impl Default for Mbc1 { | ||||
|     fn default() -> Self { | ||||
| impl MBC1 { | ||||
|     fn new(ram_size: RamSize, rom_size: RomSize) -> Self { | ||||
|         Self { | ||||
|             rom_bank: 0x01, | ||||
|             memory: vec![0; ram_size.capacity() as usize], | ||||
|             ram_size, | ||||
|             rom_size, | ||||
|             ram_bank: Default::default(), | ||||
|             mode: Default::default(), | ||||
|             ram_size: Default::default(), | ||||
|             ram: Default::default(), | ||||
|             bank_count: Default::default(), | ||||
|             ram_enabled: Default::default(), | ||||
|         } | ||||
|             mem_enabled: Default::default(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
| impl Mbc1 { | ||||
|     fn zero_bank(&self) -> u8 { | ||||
|         use BankCount::*; | ||||
|         use RomSize::*; | ||||
|  | ||||
|         match self.bank_count { | ||||
|         match self.rom_size { | ||||
|             None | Four | Eight | Sixteen | ThirtyTwo => 0x00, | ||||
|             SixtyFour => (self.ram_bank & 0x01) << 5, | ||||
|             OneHundredTwentyEight => (self.ram_bank & 0x03) << 5, | ||||
|             _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count), | ||||
|             OneTwentyEight => (self.ram_bank & 0x03) << 5, | ||||
|             _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn _mbcm_zero_bank(&self) -> u8 { | ||||
|         use BankCount::*; | ||||
|         use RomSize::*; | ||||
|  | ||||
|         match self.bank_count { | ||||
|         match self.rom_size { | ||||
|             None | Four | Eight | Sixteen | ThirtyTwo => 0x00, | ||||
|             SixtyFour => (self.ram_bank & 0x03) << 4, | ||||
|             OneHundredTwentyEight => (self.ram_bank & 0x03) << 5, | ||||
|             _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count), | ||||
|             OneTwentyEight => (self.ram_bank & 0x03) << 5, | ||||
|             _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn high_bank(&self) -> u8 { | ||||
|         use BankCount::*; | ||||
|         use RomSize::*; | ||||
|  | ||||
|         let base = self.rom_bank & self.rom_size_mask(); | ||||
|  | ||||
|         match self.bank_count { | ||||
|         match self.rom_size { | ||||
|             None | Four | Eight | Sixteen | ThirtyTwo => base, | ||||
|             SixtyFour => base & !(0x01 << 5) | ((self.ram_bank & 0x01) << 5), | ||||
|             OneHundredTwentyEight => base & !(0x03 << 5) | ((self.ram_bank & 0x03) << 5), | ||||
|             _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count), | ||||
|             OneTwentyEight => base & !(0x03 << 5) | ((self.ram_bank & 0x03) << 5), | ||||
|             _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn rom_size_mask(&self) -> u8 { | ||||
|         use BankCount::*; | ||||
|         use RomSize::*; | ||||
|  | ||||
|         match self.bank_count { | ||||
|         match self.rom_size { | ||||
|             None => 0b00000001, | ||||
|             Four => 0b00000011, | ||||
|             Eight => 0b00000111, | ||||
|             Sixteen => 0b00001111, | ||||
|             ThirtyTwo | SixtyFour | OneHundredTwentyEight => 0b00011111, | ||||
|             _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count), | ||||
|             ThirtyTwo | SixtyFour | OneTwentyEight => 0b00011111, | ||||
|             _ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn ram_addr(&self, addr: u16) -> u16 { | ||||
|     fn ram_addr(&self, addr: u16) -> usize { | ||||
|         use RamSize::*; | ||||
|  | ||||
|         match self.ram_size { | ||||
|             _2KB | _8KB => (addr - 0xA000) % self.ram_size.len() as u16, | ||||
|             _32KB => { | ||||
|             Unused | One => (addr as usize - 0xA000) % self.ram_size.capacity(), | ||||
|             Four => { | ||||
|                 if self.mode { | ||||
|                     0x2000 * self.ram_bank as u16 + (addr - 0xA000) | ||||
|                     0x2000 * self.ram_bank as usize + (addr as usize - 0xA000) | ||||
|                 } else { | ||||
|                     addr - 0xA000 | ||||
|                     addr as usize - 0xA000 | ||||
|                 } | ||||
|             } | ||||
|             _ => unreachable!("RAM size can not be greater than 32KB on MBC1"), | ||||
| @@ -237,49 +197,39 @@ impl Mbc1 { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl MemoryBankController for Mbc1 { | ||||
|     fn handle_read(&self, addr: u16) -> MbcResult { | ||||
|         use MbcResult::*; | ||||
| impl MBCIo for MBC1 { | ||||
|     fn handle_read(&self, addr: u16) -> MBCResult { | ||||
|         use MBCResult::*; | ||||
|  | ||||
|         match addr { | ||||
|             0x0000..=0x3FFF => { | ||||
|                 if self.mode { | ||||
|             0x0000..=0x3FFF if self.mode => { | ||||
|                 Address(0x4000 * self.zero_bank() as usize + addr as usize) | ||||
|                 } else { | ||||
|                     Address(addr as usize) | ||||
|                 } | ||||
|             } | ||||
|             0x0000..=0x3FFF => Address(addr as usize), | ||||
|             0x4000..=0x7FFF => { | ||||
|                 Address(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000)) | ||||
|             } | ||||
|             0xA000..=0xBFFF => { | ||||
|                 if self.ram_enabled { | ||||
|                     Value(self.ram[self.ram_addr(addr) as usize]) | ||||
|                 } else { | ||||
|                     Value(0xFF) | ||||
|                 } | ||||
|             0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => { | ||||
|                 Value(self.memory[self.ram_addr(addr)]) | ||||
|             } | ||||
|             0xA000..=0xBFFF => Value(0xFF), | ||||
|             _ => unreachable!("A read from {:#06X} should not be handled by MBC1", addr), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn handle_write(&mut self, addr: u16, byte: u8) { | ||||
|         match addr { | ||||
|             0x0000..=0x1FFF => self.ram_enabled = (byte & 0x0F) == 0x0A, | ||||
|             0x0000..=0x1FFF => self.mem_enabled = (byte & 0x0F) == 0x0A, | ||||
|             0x2000..=0x3FFF => { | ||||
|                 self.rom_bank = if byte == 0x00 { | ||||
|                     0x01 | ||||
|                 } else { | ||||
|                     byte & self.rom_size_mask() | ||||
|                 }; | ||||
|  | ||||
|                 self.rom_bank &= 0x1F; | ||||
|                 let value = byte & 0x1F; | ||||
|                 let masked_value = byte & self.rom_size_mask(); | ||||
|                 self.rom_bank = if value == 0 { 0x01 } else { masked_value }; | ||||
|             } | ||||
|             0x4000..=0x5FFF => self.ram_bank = byte & 0x03, | ||||
|             0x6000..=0x7FFF => self.mode = (byte & 0x01) == 0x01, | ||||
|             0xA000..=0xBFFF if self.ram_enabled => { | ||||
|                 let ram_addr = self.ram_addr(addr) as usize; | ||||
|                 self.ram[ram_addr] = byte; | ||||
|             0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => { | ||||
|                 let ram_addr = self.ram_addr(addr); | ||||
|                 self.memory[ram_addr] = byte; | ||||
|             } | ||||
|             0xA000..=0xBFFF => {} // Ram isn't enabled, ignored write | ||||
|             _ => unreachable!("A write to {:#06X} should not be handled by MBC1", addr), | ||||
| @@ -293,32 +243,47 @@ enum MBC3Device { | ||||
|     RealTimeClock, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Default)] | ||||
| #[derive(Debug)] | ||||
| struct MBC3 { | ||||
|     /// 7-bit Number | ||||
|     rom_bank: u8, | ||||
|     /// 2-bit Number | ||||
|     ram_bank: u8, | ||||
|  | ||||
|     devices_enabled: bool, | ||||
|     currently_mapped: Option<MBC3Device>, | ||||
|     ram_size: RamSize, | ||||
|     ram: Vec<u8>, | ||||
|     devs_enabled: bool, | ||||
|     mapped: Option<MBC3Device>, | ||||
|     memory: Vec<u8>, | ||||
|  | ||||
|     // RTC Data Latch Previous Write | ||||
|     prev_latch_write: Option<u8>, | ||||
| } | ||||
|  | ||||
| impl MemoryBankController for MBC3 { | ||||
|     fn handle_read(&self, addr: u16) -> MbcResult { | ||||
|         use MbcResult::*; | ||||
| impl MBC3 { | ||||
|     fn new(ram_cap: usize) -> Self { | ||||
|         Self { | ||||
|             memory: vec![0; ram_cap], | ||||
|             rom_bank: Default::default(), | ||||
|             ram_bank: Default::default(), | ||||
|             devs_enabled: Default::default(), | ||||
|             mapped: Default::default(), | ||||
|             prev_latch_write: Default::default(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl MBCIo for MBC3 { | ||||
|     fn handle_read(&self, addr: u16) -> MBCResult { | ||||
|         use MBCResult::*; | ||||
|  | ||||
|         let res = match addr { | ||||
|             0x0000..=0x3FFF => Address(addr as usize), | ||||
|             0x4000..=0x7FFF => Address(0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)), | ||||
|             0xA000..=0xBFFF => match self.currently_mapped { | ||||
|                 Some(MBC3Device::ExternalRam) if self.devices_enabled => { | ||||
|                     Value(self.ram[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)]) | ||||
|             0xA000..=0xBFFF => match self.mapped { | ||||
|                 Some(MBC3Device::ExternalRam) if self.devs_enabled => { | ||||
|                     Value(self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)]) | ||||
|                 } | ||||
|                 Some(MBC3Device::RealTimeClock) if self.devices_enabled => { | ||||
|                     unimplemented!("Reading from MBC3 RTC is currently unsupported") | ||||
|                 Some(MBC3Device::RealTimeClock) if self.devs_enabled => { | ||||
|                     todo!("Return Latched value of register") | ||||
|                 } | ||||
|                 _ => Value(0xFF), | ||||
|             }, | ||||
| @@ -330,29 +295,37 @@ impl MemoryBankController for MBC3 { | ||||
|  | ||||
|     fn handle_write(&mut self, addr: u16, byte: u8) { | ||||
|         match addr { | ||||
|             0x000..=0x1FFF => self.devices_enabled = (byte & 0x0F) == 0x0A, // Enable External RAM and Access to RTC if there is one | ||||
|             0x2000..=0x3FFF => match byte { | ||||
|                 0x00 => self.rom_bank = 0x01, | ||||
|                 byte => self.rom_bank = byte & 0x7F, | ||||
|             }, | ||||
|             0x000..=0x1FFF => self.devs_enabled = (byte & 0x0F) == 0x0A, // Enable External RAM and Access to RTC if there is one | ||||
|             0x2000..=0x3FFF => { | ||||
|                 self.rom_bank = match byte { | ||||
|                     0x00 => 0x01, | ||||
|                     byte => byte & 0x7F, | ||||
|                 } | ||||
|             } | ||||
|             0x4000..=0x5FFF => match byte { | ||||
|                 0x00 | 0x01 | 0x02 | 0x03 => { | ||||
|                     self.ram_bank = byte & 0x03; | ||||
|                     self.currently_mapped = Some(MBC3Device::ExternalRam); | ||||
|                     self.mapped = Some(MBC3Device::ExternalRam); | ||||
|                 } | ||||
|                 0x08 | 0x09 | 0x0A | 0x0B | 0x0C => { | ||||
|                     self.currently_mapped = Some(MBC3Device::RealTimeClock); | ||||
|                     unimplemented!("RTC in MBC3 is currently unimplemented") | ||||
|                     self.mapped = Some(MBC3Device::RealTimeClock); | ||||
|                 } | ||||
|                 _ => {} | ||||
|             }, | ||||
|             0x6000..=0x7FFF => unimplemented!("RTC Data Latch is currently unimplemented"), | ||||
|             0xA000..=0xBFFF => match self.currently_mapped { | ||||
|                 Some(MBC3Device::ExternalRam) if self.devices_enabled => { | ||||
|                     self.ram[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte | ||||
|             0x6000..=0x7FFF => { | ||||
|                 if let Some(0x00) = self.prev_latch_write { | ||||
|                     if byte == 0x01 { | ||||
|                         todo!("Perform Data Latch") | ||||
|                     } | ||||
|                 Some(MBC3Device::RealTimeClock) if self.devices_enabled => { | ||||
|                     unimplemented!("Writing to MBC3 RTC is currently unsupported") | ||||
|                 } | ||||
|                 self.prev_latch_write = Some(byte); | ||||
|             } | ||||
|             0xA000..=0xBFFF => match self.mapped { | ||||
|                 Some(MBC3Device::ExternalRam) if self.devs_enabled => { | ||||
|                     self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte | ||||
|                 } | ||||
|                 Some(MBC3Device::RealTimeClock) if self.devs_enabled => { | ||||
|                     todo!("Write to RTC") | ||||
|                 } | ||||
|                 _ => {} | ||||
|             }, | ||||
| @@ -361,12 +334,132 @@ impl MemoryBankController for MBC3 { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| struct NoMbc {} | ||||
| #[derive(Debug)] | ||||
| struct MBC5 { | ||||
|     /// 9-bit number | ||||
|     rom_bank: u16, | ||||
|     /// 4-bit number | ||||
|     ram_bank: u8, | ||||
|  | ||||
| impl MemoryBankController for NoMbc { | ||||
|     fn handle_read(&self, addr: u16) -> MbcResult { | ||||
|         MbcResult::Address(addr as usize) | ||||
|     rom_cap: usize, | ||||
|  | ||||
|     memory: Vec<u8>, | ||||
|     mem_enabled: bool, | ||||
| } | ||||
|  | ||||
| impl MBC5 { | ||||
|     fn new(ram_cap: usize, rom_cap: usize) -> Self { | ||||
|         Self { | ||||
|             rom_bank: 0x01, | ||||
|             memory: vec![0; ram_cap], | ||||
|             rom_cap, | ||||
|             ram_bank: Default::default(), | ||||
|             mem_enabled: Default::default(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn bank_addr(&self, addr: u16) -> usize { | ||||
|         (0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)) % self.rom_cap | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl MBCIo for MBC5 { | ||||
|     fn handle_read(&self, addr: u16) -> MBCResult { | ||||
|         use MBCResult::*; | ||||
|  | ||||
|         match addr { | ||||
|             0x0000..=0x3FFF => Address(addr as usize), | ||||
|             0x4000..=0x7FFF => Address(self.bank_addr(addr)), | ||||
|             0xA000..=0xBFFF if self.mem_enabled => { | ||||
|                 Value(self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)]) | ||||
|             } | ||||
|             0xA000..=0xBFFF => Value(0xFF), | ||||
|             _ => unreachable!("A read from {:#06X} should not be handled by MBC5", addr), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn handle_write(&mut self, addr: u16, byte: u8) { | ||||
|         match addr { | ||||
|             0x0000..=0x1FFF => self.mem_enabled = (byte & 0x0F) == 0x0A, | ||||
|             0x2000..=0x2FFF => self.rom_bank = (self.rom_bank & 0x0100) | byte as u16, | ||||
|             0x3000..=0x3FFF => self.rom_bank = (self.rom_bank & 0x00FF) | (byte as u16 & 0x01) << 8, | ||||
|             0x4000..=0x5FFF => self.ram_bank = byte & 0x0F, | ||||
|             0xA000..=0xBFFF if self.mem_enabled => { | ||||
|                 self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte; | ||||
|             } | ||||
|             0xA000..=0xBFFF => {} | ||||
|             _ => unreachable!("A write to {:#06X} should not be handled by MBC5", addr), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| struct MBC2 { | ||||
|     /// 4-bit number | ||||
|     rom_bank: u8, | ||||
|     memory: Box<[u8; Self::RAM_SIZE]>, | ||||
|     mem_enabled: bool, | ||||
|  | ||||
|     rom_cap: usize, | ||||
| } | ||||
|  | ||||
| impl MBC2 { | ||||
|     const RAM_SIZE: usize = 0x0200; | ||||
|  | ||||
|     fn new(rom_cap: usize) -> Self { | ||||
|         Self { | ||||
|             rom_bank: 0x01, | ||||
|             memory: Box::new([0; Self::RAM_SIZE]), | ||||
|             mem_enabled: Default::default(), | ||||
|             rom_cap, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn rom_addr(&self, addr: u16) -> usize { | ||||
|         (0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)) % self.rom_cap | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl MBCIo for MBC2 { | ||||
|     fn handle_read(&self, addr: u16) -> MBCResult { | ||||
|         use MBCResult::*; | ||||
|  | ||||
|         match addr { | ||||
|             0x0000..=0x3FFF => Address(addr as usize), | ||||
|             0x4000..=0x7FFF => Address(self.rom_addr(addr)), | ||||
|             0xA000..=0xBFFF if self.mem_enabled => { | ||||
|                 let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1); | ||||
|                 Value(self.memory[mbc2_addr] | 0xF0) | ||||
|             } | ||||
|             0xA000..=0xBFFF => Value(0xFF), | ||||
|             _ => unreachable!("A read from {:#06X} should not be handled by MBC2", addr), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn handle_write(&mut self, addr: u16, byte: u8) { | ||||
|         let nybble = byte & 0x0F; | ||||
|  | ||||
|         match addr { | ||||
|             0x0000..=0x3FFF if addr >> 8 & 0x01 == 0x01 => { | ||||
|                 self.rom_bank = if nybble == 0x00 { 0x01 } else { nybble }; | ||||
|             } | ||||
|             0x0000..=0x3FFF => self.mem_enabled = nybble == 0x0A, | ||||
|             0xA000..=0xBFFF if self.mem_enabled => { | ||||
|                 let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1); | ||||
|                 self.memory[mbc2_addr] = nybble; | ||||
|             } | ||||
|             0x4000..=0x7FFF | 0xA000..=0xBFFF => {} | ||||
|             _ => unreachable!("A write to {:#06X} should not be handled by MBC2", addr), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| struct NoMBC; | ||||
|  | ||||
| impl MBCIo for NoMBC { | ||||
|     fn handle_read(&self, addr: u16) -> MBCResult { | ||||
|         MBCResult::Address(addr as usize) | ||||
|     } | ||||
|  | ||||
|     fn handle_write(&mut self, _addr: u16, _byte: u8) { | ||||
| @@ -374,67 +467,57 @@ impl MemoryBankController for NoMbc { | ||||
|     } | ||||
| } | ||||
|  | ||||
| trait MemoryBankController: CloneMbc { | ||||
|     fn handle_read(&self, addr: u16) -> MbcResult; | ||||
| trait MBCIo { | ||||
|     fn handle_read(&self, addr: u16) -> MBCResult; | ||||
|     fn handle_write(&mut self, addr: u16, byte: u8); | ||||
| } | ||||
|  | ||||
| trait CloneMbc { | ||||
|     fn clone_mbc(&self) -> Box<dyn MemoryBankController>; | ||||
| } | ||||
|  | ||||
| impl<T> CloneMbc for T | ||||
| where | ||||
|     T: MemoryBankController + Clone + 'static, | ||||
| { | ||||
|     fn clone_mbc<'a>(&self) -> Box<dyn MemoryBankController> { | ||||
|         Box::new(self.clone()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| enum MbcResult { | ||||
| enum MBCResult { | ||||
|     Address(usize), | ||||
|     Value(u8), | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| enum MbcKind { | ||||
| enum MBCKind { | ||||
|     None, | ||||
|     Mbc1, | ||||
|     Mbc1WithBattery, | ||||
|     Mbc5, | ||||
|     Mbc3WithBattery, | ||||
|     Mbc3, | ||||
|     MBC1, | ||||
|     MBC1WithBattery, | ||||
|     MBC2, | ||||
|     MBC2WithBattery, | ||||
|     MBC3, | ||||
|     MBC3WithBattery, | ||||
|     MBC5, | ||||
|     MBC5WithBattery, | ||||
| } | ||||
|  | ||||
| impl Default for MbcKind { | ||||
| impl Default for MBCKind { | ||||
|     fn default() -> Self { | ||||
|         Self::None | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| #[derive(Debug, Clone, Copy, PartialEq)] | ||||
| enum RamSize { | ||||
|     None = 0x00, | ||||
|     _2KB = 0x01, | ||||
|     _8KB = 0x02, | ||||
|     _32KB = 0x03,  // Split into 4 RAM banks | ||||
|     _128KB = 0x04, // Split into 16 RAM banks | ||||
|     _64KB = 0x05,  // Split into 8 RAm Banks | ||||
|     Unused = 0x01, | ||||
|     One = 0x02, | ||||
|     Four = 0x03, | ||||
|     Sixteen = 0x04, | ||||
|     Eight = 0x05, | ||||
| } | ||||
|  | ||||
| impl RamSize { | ||||
|     fn len(&self) -> u32 { | ||||
|     fn capacity(&self) -> usize { | ||||
|         use RamSize::*; | ||||
|  | ||||
|         match *self { | ||||
|             None => 0, | ||||
|             _2KB => 2_048, | ||||
|             _8KB => 8_192, | ||||
|             _32KB => 32_768, | ||||
|             _128KB => 131_072, | ||||
|             _64KB => 65_536, | ||||
|             Unused => 0x800, | ||||
|             One => 0x2000, | ||||
|             Four => 0x8000, | ||||
|             Sixteen => 0x20000, | ||||
|             Eight => 0x10000, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -451,63 +534,49 @@ impl From<u8> for RamSize { | ||||
|  | ||||
|         match byte { | ||||
|             0x00 => None, | ||||
|             0x01 => _2KB, | ||||
|             0x02 => _8KB, | ||||
|             0x03 => _32KB, | ||||
|             0x04 => _128KB, | ||||
|             0x05 => _64KB, | ||||
|             0x01 => Unused, | ||||
|             0x02 => One, | ||||
|             0x03 => Four, | ||||
|             0x04 => Sixteen, | ||||
|             0x05 => Eight, | ||||
|             _ => unreachable!("{:#04X} is not a valid value for RAMSize", byte), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| enum BankCount { | ||||
| enum RomSize { | ||||
|     None = 0x00,              // 32KB (also called Two) | ||||
|     Four = 0x01,              // 64KB | ||||
|     Eight = 0x02,             // 128KB | ||||
|     Sixteen = 0x03,           // 256KB | ||||
|     ThirtyTwo = 0x04,         // 512KB | ||||
|     SixtyFour = 0x05,         // 1MB | ||||
|     OneHundredTwentyEight = 0x06, // 2MB | ||||
|     TwoHundredFiftySix = 0x07,    // 4MB | ||||
|     OneTwentyEight = 0x06,    // 2MB | ||||
|     TwoFiftySix = 0x07,       // 4MB | ||||
|     FiveHundredTwelve = 0x08, // 8MB | ||||
|     SeventyTwo = 0x52,        // 1.1MB | ||||
|     Eighty = 0x53,            // 1.2MB | ||||
|     NinetySix = 0x54,         // 1.5MB | ||||
| } | ||||
|  | ||||
| impl Default for BankCount { | ||||
|     fn default() -> Self { | ||||
|         Self::None | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl BankCount { | ||||
| impl RomSize { | ||||
|     // https://hacktix.github.io/GBEDG/mbcs/#rom-size | ||||
|     fn size(self) -> u32 { | ||||
|         use BankCount::*; | ||||
|     fn capacity(&self) -> usize { | ||||
|         use RomSize::*; | ||||
|  | ||||
|         match self { | ||||
|             None => 32_768, | ||||
|             Four => 65_536, | ||||
|             Eight => 131_072, | ||||
|             Sixteen => 262_144, | ||||
|             ThirtyTwo => 524_288, | ||||
|             SixtyFour => 1_048_576, | ||||
|             OneHundredTwentyEight => 2_097_152, | ||||
|             TwoHundredFiftySix => 4_194_304, | ||||
|             FiveHundredTwelve => 8_388_608, | ||||
|             SeventyTwo => 1_179_648, | ||||
|             Eighty => 1_310_720, | ||||
|             NinetySix => 1_572_864, | ||||
|             SeventyTwo => 0x120000, | ||||
|             Eighty => 0x140000, | ||||
|             NinetySix => 0x180000, | ||||
|             _ => 0x8000 << *self as u8, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for BankCount { | ||||
| impl From<u8> for RomSize { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         use BankCount::*; | ||||
|         use RomSize::*; | ||||
|  | ||||
|         match byte { | ||||
|             0x00 => None, | ||||
| @@ -516,8 +585,8 @@ impl From<u8> for BankCount { | ||||
|             0x03 => Sixteen, | ||||
|             0x04 => ThirtyTwo, | ||||
|             0x05 => SixtyFour, | ||||
|             0x06 => OneHundredTwentyEight, | ||||
|             0x07 => TwoHundredFiftySix, | ||||
|             0x06 => OneTwentyEight, | ||||
|             0x07 => TwoFiftySix, | ||||
|             0x08 => FiveHundredTwelve, | ||||
|             0x52 => SeventyTwo, | ||||
|             0x53 => Eighty, | ||||
| @@ -527,20 +596,14 @@ impl From<u8> for BankCount { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl std::fmt::Debug for Box<dyn MemoryBankController> { | ||||
| impl std::fmt::Debug for Box<dyn MBCIo> { | ||||
|     fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|         todo!("Implement Debug for Box<dyn MBC> Trait Object"); | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl std::clone::Clone for Box<dyn MemoryBankController> { | ||||
|     fn clone(&self) -> Self { | ||||
|         self.clone_mbc() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for Box<dyn MemoryBankController> { | ||||
| impl Default for Box<dyn MBCIo> { | ||||
|     fn default() -> Self { | ||||
|         Box::new(Mbc1::default()) | ||||
|         Box::new(NoMBC) | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										315
									
								
								src/cpu.rs
									
									
									
									
									
								
							
							
						
						
									
										315
									
								
								src/cpu.rs
									
									
									
									
									
								
							| @@ -1,26 +1,21 @@ | ||||
| use crate::bus::{Bus, BusIo}; | ||||
| use crate::instruction::{Cycle, Instruction}; | ||||
| use crate::bus::{Bus, BusIo, BOOT_SIZE}; | ||||
| use crate::instruction::Instruction; | ||||
| use crate::interrupt::{InterruptEnable, InterruptFlag}; | ||||
| use crate::joypad::Joypad; | ||||
| use crate::ppu::Ppu; | ||||
| use crate::sound::AudioSender; | ||||
| use crate::timer::Timer; | ||||
| use crate::Cycle; | ||||
| use bitfield::bitfield; | ||||
| use std::fmt::{Display, Formatter, Result as FmtResult}; | ||||
|  | ||||
| #[derive(Debug, Clone, Default)] | ||||
| #[derive(Debug, Default)] | ||||
| pub struct Cpu { | ||||
|     pub bus: Bus, | ||||
|     reg: Registers, | ||||
|     flags: Flags, | ||||
|     ime: ImeState, | ||||
|     // TODO: Merge halted and state properties | ||||
|     halted: Option<HaltState>, | ||||
|     state: State, | ||||
| } | ||||
|  | ||||
| impl Cpu { | ||||
|     pub fn new() -> Self { | ||||
|     pub(crate) fn without_boot() -> Self { | ||||
|         Self { | ||||
|             reg: Registers { | ||||
|                 a: 0x01, | ||||
| @@ -38,15 +33,11 @@ impl Cpu { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub fn boot_new(path: &str) -> anyhow::Result<Self> { | ||||
|         Ok(Self { | ||||
|             bus: Bus::with_boot(path)?, | ||||
|     pub(crate) fn with_boot(rom: [u8; BOOT_SIZE]) -> Self { | ||||
|         Self { | ||||
|             bus: Bus::with_boot(rom), | ||||
|             ..Default::default() | ||||
|         }) | ||||
|         } | ||||
|  | ||||
|     pub fn set_audio_src(&mut self, sender: AudioSender) { | ||||
|         self.bus.pass_audio_src(sender) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn ime(&self) -> ImeState { | ||||
| @@ -57,89 +48,100 @@ impl Cpu { | ||||
|         self.ime = state; | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn halt(&mut self, state: HaltState) { | ||||
|         self.halted = Some(state); | ||||
|     pub(crate) fn halt_cpu(&mut self, kind: HaltKind) { | ||||
|         self.state = State::Halt(kind); | ||||
|     } | ||||
|  | ||||
|     fn resume(&mut self) { | ||||
|         self.halted = None; | ||||
|     fn resume_execution(&mut self) { | ||||
|         self.state = State::Execute; | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn halted(&self) -> Option<HaltState> { | ||||
|         self.halted | ||||
|     pub(crate) fn is_halted(&self) -> bool { | ||||
|         match self.state { | ||||
|             State::Halt(_) => true, | ||||
|             _ => false, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn inc_pc(&mut self) { | ||||
|         self.reg.pc += 1; | ||||
|     pub(crate) fn halt_kind(&self) -> Option<HaltKind> { | ||||
|         match self.state { | ||||
|             State::Halt(kind) => Some(kind), | ||||
|             _ => None, | ||||
|         } | ||||
|  | ||||
|     pub fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> { | ||||
|         self.bus.load_cartridge(path) | ||||
|     } | ||||
|  | ||||
|     pub fn rom_title(&self) -> Option<&str> { | ||||
|         self.bus.rom_title() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Cpu { | ||||
|     fn fetch(&self) -> u8 { | ||||
|         self.bus.read_byte(self.reg.pc) | ||||
|     /// Fetch an [Instruction] from the memory bus | ||||
|     /// (4 cycles) | ||||
|     fn fetch(&mut self) -> u8 { | ||||
|         let byte = self.read_byte(self.reg.pc); | ||||
|         self.bus.clock(); | ||||
|         self.reg.pc += 1; | ||||
|         byte | ||||
|     } | ||||
|  | ||||
|     /// Decode a byte into an [SM83](Cpu) [Instruction] | ||||
|     /// | ||||
|     /// If opcode == 0xCB, then decoding costs 4 cycles. | ||||
|     /// Otherwise, decoding is free | ||||
|     pub(crate) fn decode(&mut self, opcode: u8) -> Instruction { | ||||
|         Instruction::from_byte(self, opcode) | ||||
|         if opcode == 0xCB { | ||||
|             Instruction::decode(self.fetch(), true) | ||||
|         } else { | ||||
|             Instruction::decode(opcode, false) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Execute an [Instruction]. | ||||
|     /// | ||||
|     /// The amount of cycles necessary to execute an instruction range from | ||||
|     /// 0 to 20 T-cycles | ||||
|     fn execute(&mut self, instruction: Instruction) -> Cycle { | ||||
|         Instruction::execute(self, instruction) | ||||
|     } | ||||
|  | ||||
|     /// Perform the [`Cpu::fetch()`] [`Cpu::decode()`] [`Cpu::execute()`] | ||||
|     /// routine. | ||||
|     /// | ||||
|     /// Handle HALT and interrupts. | ||||
|     pub fn step(&mut self) -> Cycle { | ||||
|         // if !self.bus.boot_enabled() { | ||||
|         // Log instructions | ||||
|         // if self.reg.pc > 0xFF { | ||||
|         //     let out = std::io::stdout(); | ||||
|         //     let handle = out.lock(); | ||||
|  | ||||
|         //     self.log_state(handle).unwrap(); | ||||
|         //     let _ = self._print_logs(out.lock()); | ||||
|         // } | ||||
|  | ||||
|         let cycles = match self.halted() { | ||||
|             Some(state) => { | ||||
|                 use HaltState::*; | ||||
|         if let Some(elapsed) = self.handle_interrupt() { | ||||
|             return elapsed; | ||||
|         } | ||||
|  | ||||
|                 match state { | ||||
|                     ImeEnabled | NonePending => Cycle::new(4), | ||||
|         if let Some(kind) = self.halt_kind() { | ||||
|             use HaltKind::*; | ||||
|  | ||||
|             self.bus.clock(); | ||||
|  | ||||
|             let elapsed = match kind { | ||||
|                 ImeEnabled | NonePending => 4, | ||||
|                 SomePending => todo!("Implement HALT bug"), | ||||
|                 } | ||||
|             } | ||||
|             None => { | ||||
|                 let opcode = self.fetch(); | ||||
|                 self.inc_pc(); | ||||
|  | ||||
|                 let instr = self.decode(opcode); | ||||
|                 let cycles = self.execute(instr); | ||||
|  | ||||
|                 self.check_ime(); | ||||
|  | ||||
|                 cycles | ||||
|             } | ||||
|             }; | ||||
|  | ||||
|         // FIXME: Regression, this is not a proper fix, | ||||
|         // the FIFO behaves weirdly using this code | ||||
|         let pending: u32 = cycles.into(); | ||||
|         let mut offset = 0; | ||||
|         for _ in 0..(pending + offset) { | ||||
|             if !self.bus.is_mpsc_still_full() { | ||||
|                 self.bus.clock(); | ||||
|             } else { | ||||
|                 offset += 1; | ||||
|             } | ||||
|             return elapsed; | ||||
|         } | ||||
|  | ||||
|         self.handle_interrupts(); | ||||
|         let opcode = self.fetch(); | ||||
|         let instr = self.decode(opcode); | ||||
|         let elapsed = self.execute(instr); | ||||
|         self.handle_ei(); | ||||
|  | ||||
|         cycles | ||||
|         // For use in Blargg's Test ROMs | ||||
|         if self.read_byte(0xFF02) == 0x81 { | ||||
|             let c = self.read_byte(0xFF01) as char; | ||||
|             self.write_byte(0xFF02, 0x00); | ||||
|             eprint!("{}", c); | ||||
|         } | ||||
|  | ||||
|         elapsed | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -154,98 +156,80 @@ impl BusIo for Cpu { | ||||
| } | ||||
|  | ||||
| impl Cpu { | ||||
|     pub(crate) fn read_imm_byte(&mut self, addr: u16) -> u8 { | ||||
|         self.inc_pc(); // NB: the addr read in the line below will be equal to PC - 1 after this function call | ||||
|         self.bus.read_byte(addr) | ||||
|     pub(crate) fn bus(&self) -> &Bus { | ||||
|         &self.bus | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn read_imm_word(&mut self, addr: u16) -> u16 { | ||||
|         self.inc_pc(); | ||||
|         self.inc_pc(); // NB: the addr read in the line below will be equal to PC - 2 after this function call | ||||
|         self.bus.read_word(addr) | ||||
|     pub(crate) fn bus_mut(&mut self) -> &mut Bus { | ||||
|         &mut self.bus | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn write_word(&mut self, addr: u16, word: u16) { | ||||
|         self.bus.write_word(addr, word) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Cpu { | ||||
|     pub fn ppu(&mut self) -> &Ppu { | ||||
|         &self.bus.ppu | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn joypad_mut(&mut self) -> &mut Joypad { | ||||
|         &mut self.bus.joypad | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn timer(&self) -> &Timer { | ||||
|         &self.bus.timer | ||||
|     } | ||||
|  | ||||
|     fn check_ime(&mut self) { | ||||
|     fn handle_ei(&mut self) { | ||||
|         match self.ime { | ||||
|             ImeState::Pending => { | ||||
|                 // This is within the context of the EI instruction, we need to not update EI until the end of the | ||||
|                 // next executed Instruction | ||||
|                 self.ime = ImeState::PendingEnd; | ||||
|             } | ||||
|             ImeState::PendingEnd => { | ||||
|                 // The Instruction after EI has now been executed, so we want to enable the IME flag here | ||||
|                 self.ime = ImeState::Enabled; | ||||
|             } | ||||
|             ImeState::Disabled | ImeState::Enabled => {} // Do Nothing | ||||
|             ImeState::EiExecuted => self.ime = ImeState::Pending, | ||||
|             ImeState::Pending => self.ime = ImeState::Enabled, | ||||
|             ImeState::Disabled | ImeState::Enabled => {} | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn handle_interrupts(&mut self) { | ||||
|         let req = self.read_byte(0xFF0F); | ||||
|         let enabled = self.read_byte(0xFFFF); | ||||
|     pub(crate) fn int_request(&self) -> u8 { | ||||
|         self.read_byte(0xFF0F) | ||||
|     } | ||||
|  | ||||
|         if self.halted.is_some() { | ||||
|     pub(crate) fn int_enable(&self) -> u8 { | ||||
|         self.read_byte(0xFFFF) | ||||
|     } | ||||
|  | ||||
|     fn handle_interrupt(&mut self) -> Option<Cycle> { | ||||
|         let irq = self.int_request(); | ||||
|         let enable = self.int_enable(); | ||||
|  | ||||
|         // TODO: Ensure that this behaviour is correct | ||||
|         if self.is_halted() { | ||||
|             // When we're here either a HALT with IME set or | ||||
|             // a HALT with IME not set and No pending Interrupts was called | ||||
|  | ||||
|             if req & enabled != 0 { | ||||
|             if irq & enable != 0 { | ||||
|                 // The if self.ime() below correctly follows the "resuming from HALT" behaviour so | ||||
|                 // nothing actually needs to be added here. This is just documentation | ||||
|                 // since it's a bit weird why nothing is being done | ||||
|  | ||||
|                 self.resume() | ||||
|                 self.resume_execution(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if let ImeState::Enabled = self.ime() { | ||||
|             let mut req: InterruptFlag = req.into(); | ||||
|             let enabled: InterruptEnable = enabled.into(); | ||||
|         match self.ime() { | ||||
|             ImeState::Enabled => { | ||||
|                 let mut irq: InterruptFlag = irq.into(); | ||||
|                 let enable: InterruptEnable = enable.into(); | ||||
|  | ||||
|             let vector = if req.vblank() && enabled.vblank() { | ||||
|                 let rst_vector = if irq.vblank() && enable.vblank() { | ||||
|                     // Handle VBlank Interrupt | ||||
|                 req.set_vblank(false); | ||||
|                     irq.set_vblank(false); | ||||
|  | ||||
|                     // INT 40h | ||||
|                     Some(0x40) | ||||
|             } else if req.lcd_stat() && enabled.lcd_stat() { | ||||
|                 } else if irq.lcd_stat() && enable.lcd_stat() { | ||||
|                     // Handle LCD STAT Interrupt | ||||
|                 req.set_lcd_stat(false); | ||||
|                     irq.set_lcd_stat(false); | ||||
|  | ||||
|                     // INT 48h | ||||
|                     Some(0x48) | ||||
|             } else if req.timer() && enabled.timer() { | ||||
|                 } else if irq.timer() && enable.timer() { | ||||
|                     // Handle Timer Interrupt | ||||
|                 req.set_timer(false); | ||||
|                     irq.set_timer(false); | ||||
|  | ||||
|                     // INT 50h | ||||
|                     Some(0x50) | ||||
|             } else if req.serial() && enabled.serial() { | ||||
|                 } else if irq.serial() && enable.serial() { | ||||
|                     // Handle Serial Interrupt | ||||
|                 req.set_serial(false); | ||||
|                     irq.set_serial(false); | ||||
|  | ||||
|                     // INT 58h | ||||
|                     Some(0x58) | ||||
|             } else if req.joypad() && enabled.joypad() { | ||||
|                 } else if irq.joypad() && enable.joypad() { | ||||
|                     // Handle Joypad Interrupt | ||||
|                 req.set_joypad(false); | ||||
|                     irq.set_joypad(false); | ||||
|  | ||||
|                     // INT 60h | ||||
|                     Some(0x60) | ||||
| @@ -253,25 +237,27 @@ impl Cpu { | ||||
|                     None | ||||
|                 }; | ||||
|  | ||||
|             let _ = match vector { | ||||
|                 Some(address) => { | ||||
|                 match rst_vector { | ||||
|                     Some(vector) => { | ||||
|                         //  Write the Changes to 0xFF0F and 0xFFFF registers | ||||
|                     self.write_byte(0xFF0F, req.into()); | ||||
|                         self.write_byte(0xFF0F, irq.into()); | ||||
|  | ||||
|                         // Disable all future interrupts | ||||
|                         self.set_ime(ImeState::Disabled); | ||||
|                     Instruction::reset(self, address) | ||||
|                         Some(Instruction::reset(self, vector)) | ||||
|                     } | ||||
|                 None => Cycle::new(0), // NO Interrupts were enabled and / or requested | ||||
|             }; | ||||
|                     None => None, | ||||
|                 } | ||||
|             } | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Copy, Clone)] | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| enum State { | ||||
|     Execute, | ||||
|     // Halt, | ||||
|     Halt(HaltKind), | ||||
|     // Stop, | ||||
| } | ||||
|  | ||||
| @@ -293,7 +279,6 @@ impl Cpu { | ||||
|             E => self.reg.e = value, | ||||
|             H => self.reg.h = value, | ||||
|             L => self.reg.l = value, | ||||
|             Flag => self.flags = value.into(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -308,7 +293,6 @@ impl Cpu { | ||||
|             E => self.reg.e, | ||||
|             H => self.reg.h, | ||||
|             L => self.reg.l, | ||||
|             Flag => self.flags.into(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -357,13 +341,20 @@ impl Cpu { | ||||
|         &self.flags | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn update_flags(&mut self, z: bool, n: bool, h: bool, c: bool) { | ||||
|         self.flags.set_z(z); | ||||
|         self.flags.set_n(n); | ||||
|         self.flags.set_h(h); | ||||
|         self.flags.set_c(c); | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn set_flags(&mut self, flags: Flags) { | ||||
|         self.flags = flags; | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Cpu { | ||||
|     fn _debug_log(&self, mut w: impl std::io::Write, instr: &Instruction) -> std::io::Result<()> { | ||||
|     fn _print_debug(&self, mut w: impl std::io::Write) -> std::io::Result<()> { | ||||
|         write!(w, "A: {:02X} ", self.reg.a)?; | ||||
|         write!(w, "F: {:02X} ", u8::from(self.flags))?; | ||||
|         write!(w, "B: {:02X} ", self.reg.b)?; | ||||
| @@ -378,30 +369,39 @@ impl Cpu { | ||||
|         write!(w, "{:02X} ", self.read_byte(self.reg.pc + 1))?; | ||||
|         write!(w, "{:02X} ", self.read_byte(self.reg.pc + 2))?; | ||||
|         write!(w, "{:02X})", self.read_byte(self.reg.pc + 3))?; | ||||
|         writeln!(w, "| {:?}", instr)?; | ||||
|         writeln!(w, "| {:?}", self._dbg_instr())?; | ||||
|         w.flush() | ||||
|     } | ||||
|  | ||||
|     fn _log_state(&self, mut writer: impl std::io::Write) -> std::io::Result<()> { | ||||
|         write!(writer, "A: {:02X} ", self.reg.a)?; | ||||
|         write!(writer, "F: {:02X} ", u8::from(self.flags))?; | ||||
|         write!(writer, "B: {:02X} ", self.reg.b)?; | ||||
|         write!(writer, "C: {:02X} ", self.reg.c)?; | ||||
|         write!(writer, "D: {:02X} ", self.reg.d)?; | ||||
|         write!(writer, "E: {:02X} ", self.reg.e)?; | ||||
|         write!(writer, "H: {:02X} ", self.reg.h)?; | ||||
|         write!(writer, "L: {:02X} ", self.reg.l)?; | ||||
|         write!(writer, "SP: {:04X} ", self.reg.sp)?; | ||||
|         write!(writer, "PC: 00:{:04X} ", self.reg.pc)?; | ||||
|         write!(writer, "({:02X} ", self.read_byte(self.reg.pc))?; | ||||
|         write!(writer, "{:02X} ", self.read_byte(self.reg.pc + 1))?; | ||||
|         write!(writer, "{:02X} ", self.read_byte(self.reg.pc + 2))?; | ||||
|         writeln!(writer, "{:02X})", self.read_byte(self.reg.pc + 3))?; | ||||
|         writer.flush() | ||||
|     fn _print_logs(&self, mut w: impl std::io::Write) -> std::io::Result<()> { | ||||
|         write!(w, "A: {:02X} ", self.reg.a)?; | ||||
|         write!(w, "F: {:02X} ", u8::from(self.flags))?; | ||||
|         write!(w, "B: {:02X} ", self.reg.b)?; | ||||
|         write!(w, "C: {:02X} ", self.reg.c)?; | ||||
|         write!(w, "D: {:02X} ", self.reg.d)?; | ||||
|         write!(w, "E: {:02X} ", self.reg.e)?; | ||||
|         write!(w, "H: {:02X} ", self.reg.h)?; | ||||
|         write!(w, "L: {:02X} ", self.reg.l)?; | ||||
|         write!(w, "SP: {:04X} ", self.reg.sp)?; | ||||
|         write!(w, "PC: 00:{:04X} ", self.reg.pc)?; | ||||
|         write!(w, "({:02X} ", self.read_byte(self.reg.pc))?; | ||||
|         write!(w, "{:02X} ", self.read_byte(self.reg.pc + 1))?; | ||||
|         write!(w, "{:02X} ", self.read_byte(self.reg.pc + 2))?; | ||||
|         writeln!(w, "{:02X})", self.read_byte(self.reg.pc + 3))?; | ||||
|         w.flush() | ||||
|     } | ||||
|  | ||||
|     fn _dbg_instr(&self) -> Instruction { | ||||
|         let byte = self.read_byte(self.reg.pc); | ||||
|         if byte == 0xCB { | ||||
|             Instruction::decode(self.read_byte(self.reg.pc + 1), true) | ||||
|         } else { | ||||
|             Instruction::decode(byte, false) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Copy, Clone)] | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| pub(crate) enum Register { | ||||
|     A, | ||||
|     B, | ||||
| @@ -410,10 +410,9 @@ pub(crate) enum Register { | ||||
|     E, | ||||
|     H, | ||||
|     L, | ||||
|     Flag, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Copy, Clone)] | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| pub(crate) enum RegisterPair { | ||||
|     AF, | ||||
|     BC, | ||||
| @@ -423,7 +422,7 @@ pub(crate) enum RegisterPair { | ||||
|     PC, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Copy, Clone, Default)] | ||||
| #[derive(Debug, Default)] | ||||
| struct Registers { | ||||
|     a: u8, | ||||
|     b: u8, | ||||
| @@ -508,7 +507,7 @@ impl From<u8> for Flags { | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| pub(crate) enum HaltState { | ||||
| pub(crate) enum HaltKind { | ||||
|     ImeEnabled, | ||||
|     NonePending, | ||||
|     SomePending, | ||||
| @@ -517,8 +516,8 @@ pub(crate) enum HaltState { | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| pub(crate) enum ImeState { | ||||
|     Disabled, | ||||
|     EiExecuted, | ||||
|     Pending, | ||||
|     PendingEnd, | ||||
|     Enabled, | ||||
| } | ||||
|  | ||||
|   | ||||
							
								
								
									
										137
									
								
								src/emu.rs
									
									
									
									
									
								
							
							
						
						
									
										137
									
								
								src/emu.rs
									
									
									
									
									
								
							| @@ -1,60 +1,121 @@ | ||||
| use crate::cpu::Cpu as SM83; | ||||
| use crate::instruction::Cycle; | ||||
| use crate::joypad; | ||||
| use crate::ppu::Ppu; | ||||
| use anyhow::Result; | ||||
| use crate::apu::gen::SampleProducer; | ||||
| use crate::cpu::Cpu; | ||||
| use crate::joypad::{self, Joypad}; | ||||
| use crate::{Cycle, GB_HEIGHT, GB_WIDTH}; | ||||
| use gilrs::Gilrs; | ||||
| use std::time::Duration; | ||||
| use winit_input_helper::WinitInputHelper; | ||||
|  | ||||
| pub const SM83_CYCLE_TIME: Duration = Duration::from_nanos(1_000_000_000 / SM83_CLOCK_SPEED); | ||||
| pub const CYCLES_IN_FRAME: Cycle = Cycle::new(456 * 154); // 456 Cycles times 154 scanlines | ||||
| pub const CYCLES_IN_FRAME: Cycle = 456 * 154; // 456 Cycles times 154 scanlines | ||||
| pub(crate) const SM83_CLOCK_SPEED: u64 = 0x40_0000; // Hz which is 4.194304Mhz | ||||
| const DEFAULT_TITLE: &str = "DMG-01 Emulator"; | ||||
| const GAMEPAD_ENABLED: bool = false; | ||||
|  | ||||
| pub fn init(boot_path: Option<&str>, rom_path: &str) -> Result<SM83> { | ||||
|     let mut cpu = match boot_path { | ||||
|         Some(path) => SM83::boot_new(path)?, | ||||
|         None => SM83::new(), | ||||
|     }; | ||||
| pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: &WinitInputHelper) -> Cycle { | ||||
|     let mut elapsed = 0; | ||||
|  | ||||
|     eprintln!("Initialized GB Emulator"); | ||||
|  | ||||
|     cpu.load_cartridge(rom_path)?; | ||||
|     Ok(cpu) | ||||
|     if let Some(event) = gamepad.next_event() { | ||||
|         joypad::handle_gamepad_input(emu.joyp_mut(), event); | ||||
|     } | ||||
|  | ||||
| pub fn rom_title(game_boy: &SM83) -> &str { | ||||
|     game_boy.rom_title().unwrap_or(DEFAULT_TITLE) | ||||
| } | ||||
|  | ||||
| pub fn run( | ||||
|     game_boy: &mut SM83, | ||||
|     gamepad: &mut Gilrs, | ||||
|     input: &WinitInputHelper, | ||||
|     pending: Cycle, | ||||
| ) -> Cycle { | ||||
|     let mut elapsed = Cycle::new(0); | ||||
|  | ||||
|     while elapsed < pending { | ||||
|         elapsed += run_unsynced(game_boy, gamepad, input); | ||||
|     joypad::handle_keyboard_input(emu.joyp_mut(), key); | ||||
|     while elapsed < CYCLES_IN_FRAME { | ||||
|         elapsed += emu.step(); | ||||
|     } | ||||
|  | ||||
|     elapsed | ||||
| } | ||||
|  | ||||
| pub fn run_unsynced(game_boy: &mut SM83, gamepad: &mut Gilrs, input: &WinitInputHelper) -> Cycle { | ||||
|     if GAMEPAD_ENABLED { | ||||
|         if let Some(event) = gamepad.next_event() { | ||||
|             joypad::handle_gamepad_input(game_boy.joypad_mut(), event); | ||||
| pub fn draw_frame(emu: &Emulator, buf: &mut [u8; GB_HEIGHT * GB_WIDTH * 4]) { | ||||
|     buf.copy_from_slice(emu.cpu.bus().ppu.frame_buf()); | ||||
| } | ||||
|  | ||||
| pub struct Emulator { | ||||
|     cpu: Cpu, | ||||
|     timestamp: Cycle, | ||||
| } | ||||
|  | ||||
| impl Emulator { | ||||
|     fn new(cpu: Cpu) -> Self { | ||||
|         Self { | ||||
|             cpu, | ||||
|             timestamp: Default::default(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     joypad::handle_keyboard_input(game_boy.joypad_mut(), input); | ||||
|     game_boy.step() | ||||
|     fn step(&mut self) -> Cycle { | ||||
|         self.cpu.step() | ||||
|     } | ||||
|  | ||||
| pub fn draw(ppu: &Ppu, frame: &mut [u8]) { | ||||
|     ppu.copy_to_gui(frame); | ||||
|     fn load_cart(&mut self, rom: Vec<u8>) { | ||||
|         self.cpu.bus_mut().load_cart(rom) | ||||
|     } | ||||
|  | ||||
|     fn joyp_mut(&mut self) -> &mut Joypad { | ||||
|         &mut self.cpu.bus_mut().joypad | ||||
|     } | ||||
|  | ||||
|     pub fn set_prod(&mut self, prod: SampleProducer<f32>) { | ||||
|         self.cpu.bus_mut().apu.attach_producer(prod) | ||||
|     } | ||||
|  | ||||
|     pub fn title(&self) -> &str { | ||||
|         self.cpu.bus().cart_title().unwrap_or(DEFAULT_TITLE) | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub mod build { | ||||
|     use std::fs::File; | ||||
|     use std::io::{Read, Result}; | ||||
|     use std::path::Path; | ||||
|  | ||||
|     use crate::bus::BOOT_SIZE; | ||||
|     use crate::cpu::Cpu; | ||||
|  | ||||
|     use super::Emulator; | ||||
|  | ||||
|     #[derive(Debug, Default)] | ||||
|     pub struct EmulatorBuilder { | ||||
|         boot: Option<[u8; BOOT_SIZE]>, | ||||
|         cart: Option<Vec<u8>>, | ||||
|     } | ||||
|  | ||||
|     impl EmulatorBuilder { | ||||
|         pub fn new() -> Self { | ||||
|             Default::default() | ||||
|         } | ||||
|  | ||||
|         pub fn with_boot<P: AsRef<Path>>(mut self, path: P) -> Result<Self> { | ||||
|             let mut file = File::open(path.as_ref())?; | ||||
|  | ||||
|             let mut buf = [0x00; BOOT_SIZE]; | ||||
|             file.read_exact(&mut buf)?; | ||||
|  | ||||
|             self.boot = Some(buf); | ||||
|             Ok(self) | ||||
|         } | ||||
|  | ||||
|         pub fn with_cart<P: AsRef<Path>>(mut self, path: P) -> Result<Self> { | ||||
|             let mut file = File::open(path.as_ref())?; | ||||
|  | ||||
|             let mut buf = Vec::new(); | ||||
|             file.read_to_end(&mut buf)?; | ||||
|  | ||||
|             self.cart = Some(buf); | ||||
|             Ok(self) | ||||
|         } | ||||
|  | ||||
|         pub fn finish(mut self) -> Emulator { | ||||
|             let mut emu = Emulator::new(match self.boot { | ||||
|                 Some(rom) => Cpu::with_boot(rom), | ||||
|                 None => Cpu::without_boot(), | ||||
|             }); | ||||
|  | ||||
|             if let Some(rom) = self.cart.take() { | ||||
|                 emu.load_cart(rom) | ||||
|             } | ||||
|  | ||||
|             emu | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										321
									
								
								src/gui.rs
									
									
									
									
									
								
							
							
						
						
									
										321
									
								
								src/gui.rs
									
									
									
									
									
								
							| @@ -1,321 +0,0 @@ | ||||
| use crate::bus::BusIo; | ||||
| use crate::cpu::{Cpu as SM83, Register, RegisterPair}; | ||||
| use egui::{ClippedMesh, FontDefinitions}; | ||||
| use egui_wgpu_backend::{RenderPass, ScreenDescriptor}; | ||||
| use egui_winit_platform::{Platform, PlatformDescriptor}; | ||||
| use pixels::{wgpu, PixelsContext}; | ||||
| use std::time::Instant; | ||||
| use wgpu::TextureFormat::Bgra8UnormSrgb; | ||||
|  | ||||
| // Boilerplate code from: https://github.com/parasyte/pixels/blob/0.3.0/examples/egui-winit/src/gui.rs | ||||
|  | ||||
| /// Manages all state required for rendering egui over `Pixels`. | ||||
| pub struct Egui { | ||||
|     // State for egui. | ||||
|     start_time: Instant, | ||||
|     platform: Platform, | ||||
|     screen_desc: ScreenDescriptor, | ||||
|     render_pass: RenderPass, | ||||
|     paint_jobs: Vec<ClippedMesh>, | ||||
|  | ||||
|     pub config: Configuration, | ||||
|  | ||||
|     show_flags: bool, | ||||
|     show_cpu_info: bool, | ||||
|     show_registers: bool, | ||||
|     show_int: bool, | ||||
|     show_timer: bool, | ||||
| } | ||||
|  | ||||
| impl Egui { | ||||
|     /// Create egui. | ||||
|     pub fn new(width: u32, height: u32, scale_factor: f64, context: &PixelsContext) -> Self { | ||||
|         let platform = Platform::new(PlatformDescriptor { | ||||
|             physical_width: width, | ||||
|             physical_height: height, | ||||
|             scale_factor, | ||||
|             font_definitions: FontDefinitions::default(), | ||||
|             style: Default::default(), | ||||
|         }); | ||||
|  | ||||
|         let screen_desc = ScreenDescriptor { | ||||
|             physical_width: width, | ||||
|             physical_height: height, | ||||
|             scale_factor: scale_factor as f32, | ||||
|         }; | ||||
|  | ||||
|         let render_pass = RenderPass::new(&context.device, Bgra8UnormSrgb); | ||||
|  | ||||
|         Self { | ||||
|             start_time: Instant::now(), | ||||
|             platform, | ||||
|             screen_desc, | ||||
|             render_pass, | ||||
|             paint_jobs: Vec::new(), | ||||
|             config: Default::default(), | ||||
|             show_flags: false, | ||||
|             show_cpu_info: false, | ||||
|             show_registers: false, | ||||
|             show_int: false, | ||||
|             show_timer: false, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Handle input events from the window manager. | ||||
|     pub fn handle_event(&mut self, event: &winit::event::Event<'_, ()>) { | ||||
|         self.platform.handle_event(event); | ||||
|     } | ||||
|  | ||||
|     /// Resize egui. | ||||
|     pub fn resize(&mut self, width: u32, height: u32) { | ||||
|         self.screen_desc.physical_width = width; | ||||
|         self.screen_desc.physical_height = height; | ||||
|     } | ||||
|  | ||||
|     /// Update scaling factor. | ||||
|     pub fn scale_factor(&mut self, scale_factor: f64) { | ||||
|         self.screen_desc.scale_factor = scale_factor as f32; | ||||
|     } | ||||
|  | ||||
|     /// Prepare egui. | ||||
|     pub fn prepare(&mut self, game_boy: &SM83) { | ||||
|         self.platform | ||||
|             .update_time(self.start_time.elapsed().as_secs_f64()); | ||||
|  | ||||
|         // Begin the egui frame. | ||||
|         self.platform.begin_frame(); | ||||
|  | ||||
|         // Draw the demo application. | ||||
|         self.ui(&self.platform.context(), game_boy); | ||||
|  | ||||
|         // End the egui frame and create all paint jobs to prepare for rendering. | ||||
|         let (_output, paint_commands) = self.platform.end_frame(); | ||||
|         self.paint_jobs = self.platform.context().tessellate(paint_commands); | ||||
|     } | ||||
|  | ||||
|     /// Create the UI using egui. | ||||
|     fn ui(&mut self, ctx: &egui::CtxRef, game_boy: &SM83) { | ||||
|         egui::TopPanel::top("menubar_container").show(ctx, |ui| { | ||||
|             egui::menu::bar(ui, |ui| { | ||||
|                 egui::menu::menu(ui, "File", |ui| { | ||||
|                     if ui.button("Configuration").clicked() { | ||||
|                         self.config.show = true; | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 egui::menu::menu(ui, "Status", |ui| { | ||||
|                     if ui.button("Flags").clicked() { | ||||
|                         self.show_flags = true; | ||||
|                     } | ||||
|  | ||||
|                     if ui.button("CPU Information").clicked() { | ||||
|                         self.show_cpu_info = true; | ||||
|                     } | ||||
|  | ||||
|                     if ui.button("Registers").clicked() { | ||||
|                         self.show_registers = true; | ||||
|                     } | ||||
|  | ||||
|                     if ui.button("Interrupts").clicked() { | ||||
|                         self.show_int = true; | ||||
|                     } | ||||
|  | ||||
|                     if ui.button("Timer").clicked() { | ||||
|                         self.show_timer = true; | ||||
|                     } | ||||
|                 }); | ||||
|             }); | ||||
|         }); | ||||
|  | ||||
|         egui::Window::new("Cpu Flags") | ||||
|             .open(&mut self.show_flags) | ||||
|             .show(ctx, |ui| { | ||||
|                 let flags = game_boy.flags(); | ||||
|  | ||||
|                 ui.horizontal(|ui| { | ||||
|                     let _ = ui.selectable_label(flags.z(), "Zero"); | ||||
|                     let _ = ui.selectable_label(flags.n(), "Negative"); | ||||
|                     let _ = ui.selectable_label(flags.h(), "Half-Carry"); | ||||
|                     let _ = ui.selectable_label(flags.c(), "Carry"); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|         egui::Window::new("Timer") | ||||
|             .open(&mut self.show_timer) | ||||
|             .show(ctx, |ui| { | ||||
|                 let timer = game_boy.timer(); | ||||
|  | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label("DIV"); | ||||
|                     ui.monospace(format!("{:#06X}", timer.divider)); | ||||
|                 }); | ||||
|  | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label("TIMA"); | ||||
|                     ui.monospace(format!("{:#04X}", timer.counter)); | ||||
|                 }); | ||||
|  | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label("TMA"); | ||||
|                     ui.monospace(format!("{:#04X}", timer.modulo)); | ||||
|                 }); | ||||
|  | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label("TAC"); | ||||
|                     ui.monospace(format!("{:?}", timer.ctrl)); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|         egui::Window::new("Registers") | ||||
|             .open(&mut self.show_registers) | ||||
|             .show(ctx, |ui| { | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label("A"); | ||||
|                     ui.monospace(format!("{:#04X}", game_boy.register(Register::A))); | ||||
|  | ||||
|                     ui.label("F"); | ||||
|                     ui.monospace(format!("{:#04X}", game_boy.register(Register::Flag))); | ||||
|                 }); | ||||
|  | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label("B"); | ||||
|                     ui.monospace(format!("{:#04X}", game_boy.register(Register::B))); | ||||
|  | ||||
|                     ui.label("C"); | ||||
|                     ui.monospace(format!("{:#04X}", game_boy.register(Register::C))); | ||||
|                 }); | ||||
|  | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label("D"); | ||||
|                     ui.monospace(format!("{:#04X}", game_boy.register(Register::D))); | ||||
|  | ||||
|                     ui.label("E"); | ||||
|                     ui.monospace(format!("{:#04X}", game_boy.register(Register::E))); | ||||
|                 }); | ||||
|  | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label("H"); | ||||
|                     ui.monospace(format!("{:#04X}", game_boy.register(Register::H))); | ||||
|  | ||||
|                     ui.label("L"); | ||||
|                     ui.monospace(format!("{:#04X}", game_boy.register(Register::L))); | ||||
|                 }); | ||||
|  | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label("AF"); | ||||
|                     ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::AF))); | ||||
|                 }); | ||||
|  | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label("BC"); | ||||
|                     ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::BC))); | ||||
|                 }); | ||||
|  | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label("DE"); | ||||
|                     ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::DE))); | ||||
|                 }); | ||||
|  | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label("HL"); | ||||
|                     ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::HL))); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|         egui::Window::new("Cpu Information") | ||||
|             .open(&mut self.show_cpu_info) | ||||
|             .show(ctx, |ui| { | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label("PC"); | ||||
|                     ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::PC))); | ||||
|                 }); | ||||
|  | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label("SP"); | ||||
|                     ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::SP))); | ||||
|                 }); | ||||
|  | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label("IME"); | ||||
|                     ui.label(format!("{:?}", game_boy.ime())); | ||||
|                 }); | ||||
|  | ||||
|                 ui.horizontal(|ui| { | ||||
|                     ui.label("HALT"); | ||||
|                     ui.label(format!("{:?}", game_boy.halted())); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|         egui::Window::new("IRQ Information") | ||||
|             .open(&mut self.show_int) | ||||
|             .show(ctx, |ui| { | ||||
|                 let req = game_boy.read_byte(0xFF0F); | ||||
|                 let enabled = game_boy.read_byte(0xFFFF); | ||||
|  | ||||
|                 ui.heading("Interrupt Requests"); | ||||
|                 ui.horizontal(|ui| { | ||||
|                     let _ = ui.selectable_label(req & 0x01 == 0x01, "VBLANK"); | ||||
|                     let _ = ui.selectable_label((req >> 1) & 0x01 == 0x01, "LCD STAT"); | ||||
|                     let _ = ui.selectable_label((req >> 2) & 0x01 == 0x01, "TIMER"); | ||||
|                     let _ = ui.selectable_label((req >> 3) & 0x01 == 0x01, "SERIAL"); | ||||
|                     let _ = ui.selectable_label((req >> 4) & 0x01 == 0x01, "JOYPAD"); | ||||
|                 }); | ||||
|  | ||||
|                 ui.heading("Interrupt Enable"); | ||||
|                 ui.horizontal(|ui| { | ||||
|                     let _ = ui.selectable_label(enabled & 0x01 == 0x01, "VBLANK"); | ||||
|                     let _ = ui.selectable_label((enabled >> 1) & 0x01 == 0x01, "LCD STAT"); | ||||
|                     let _ = ui.selectable_label((enabled >> 2) & 0x01 == 0x01, "TIMER"); | ||||
|                     let _ = ui.selectable_label((enabled >> 3) & 0x01 == 0x01, "SERIAL"); | ||||
|                     let _ = ui.selectable_label((enabled >> 4) & 0x01 == 0x01, "JOYPAD"); | ||||
|                 }); | ||||
|             }); | ||||
|  | ||||
|         egui::Window::new("Configuration") | ||||
|             .open(&mut self.config.show) | ||||
|             .show(ctx, |_ui| {}); | ||||
|     } | ||||
|  | ||||
|     /// Render egui. | ||||
|     pub fn render( | ||||
|         &mut self, | ||||
|         encoder: &mut wgpu::CommandEncoder, | ||||
|         render_target: &wgpu::TextureView, | ||||
|         context: &PixelsContext, | ||||
|     ) { | ||||
|         // Upload all resources to the GPU. | ||||
|         self.render_pass.update_texture( | ||||
|             &context.device, | ||||
|             &context.queue, | ||||
|             &self.platform.context().texture(), | ||||
|         ); | ||||
|         self.render_pass | ||||
|             .update_user_textures(&context.device, &context.queue); | ||||
|         self.render_pass.update_buffers( | ||||
|             &context.device, | ||||
|             &context.queue, | ||||
|             &self.paint_jobs, | ||||
|             &self.screen_desc, | ||||
|         ); | ||||
|  | ||||
|         // Record all render passes. | ||||
|         self.render_pass.execute( | ||||
|             encoder, | ||||
|             render_target, | ||||
|             &self.paint_jobs, | ||||
|             &self.screen_desc, | ||||
|             None, | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct Configuration { | ||||
|     /// Show Configuration egui menu | ||||
|     show: bool, | ||||
| } | ||||
|  | ||||
| impl Default for Configuration { | ||||
|     fn default() -> Self { | ||||
|         Self { show: false } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										2852
									
								
								src/instruction.rs
									
									
									
									
									
								
							
							
						
						
									
										2852
									
								
								src/instruction.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,6 +1,6 @@ | ||||
| use bitfield::bitfield; | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, Default)] | ||||
| #[derive(Debug, Default)] | ||||
| pub(crate) struct Interrupt { | ||||
|     pub(crate) flag: InterruptFlag, | ||||
|     pub(crate) enable: InterruptEnable, | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType}; | ||||
| use winit_input_helper::WinitInputHelper; | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| #[derive(Debug)] | ||||
| pub struct Joypad { | ||||
|     /// 0xFF00 | P1/JOYP - Player 1 Joypad | ||||
|     pub(crate) p1: u8, | ||||
| @@ -48,7 +48,7 @@ impl Joypad { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, Default)] | ||||
| #[derive(Debug, Default)] | ||||
| struct JoypadState { | ||||
|     // Direction Row | ||||
|     dpad_down: ButtonEvent, | ||||
| @@ -120,49 +120,57 @@ pub fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) { | ||||
|  | ||||
|     if input.key_pressed(VirtualKeyCode::Down) { | ||||
|         state.dpad_down.update(true, irq); | ||||
|     } else if input.key_released(VirtualKeyCode::Down) { | ||||
|     } | ||||
|     if input.key_released(VirtualKeyCode::Down) { | ||||
|         state.dpad_down.update(false, irq); | ||||
|     } | ||||
|  | ||||
|     if input.key_pressed(VirtualKeyCode::Up) { | ||||
|         state.dpad_up.update(true, irq); | ||||
|     } else if input.key_released(VirtualKeyCode::Up) { | ||||
|     } | ||||
|     if input.key_released(VirtualKeyCode::Up) { | ||||
|         state.dpad_up.update(false, irq); | ||||
|     } | ||||
|  | ||||
|     if input.key_pressed(VirtualKeyCode::Left) { | ||||
|         state.dpad_left.update(true, irq); | ||||
|     } else if input.key_released(VirtualKeyCode::Left) { | ||||
|     } | ||||
|     if input.key_released(VirtualKeyCode::Left) { | ||||
|         state.dpad_left.update(false, irq); | ||||
|     } | ||||
|  | ||||
|     if input.key_pressed(VirtualKeyCode::Right) { | ||||
|         state.dpad_right.update(true, irq); | ||||
|     } else if input.key_released(VirtualKeyCode::Right) { | ||||
|     } | ||||
|     if input.key_released(VirtualKeyCode::Right) { | ||||
|         state.dpad_right.update(false, irq); | ||||
|     } | ||||
|  | ||||
|     if input.key_pressed(VirtualKeyCode::T) { | ||||
|         state.start.update(true, irq); | ||||
|     } else if input.key_released(VirtualKeyCode::T) { | ||||
|     } | ||||
|     if input.key_released(VirtualKeyCode::T) { | ||||
|         state.start.update(false, irq); | ||||
|     } | ||||
|  | ||||
|     if input.key_pressed(VirtualKeyCode::Y) { | ||||
|         state.select.update(true, irq); | ||||
|     } else if input.key_released(VirtualKeyCode::Y) { | ||||
|     } | ||||
|     if input.key_released(VirtualKeyCode::Y) { | ||||
|         state.select.update(false, irq); | ||||
|     } | ||||
|  | ||||
|     if input.key_pressed(VirtualKeyCode::Z) { | ||||
|         state.south.update(true, irq); | ||||
|     } else if input.key_released(VirtualKeyCode::Z) { | ||||
|     } | ||||
|     if input.key_released(VirtualKeyCode::Z) { | ||||
|         state.south.update(false, irq); | ||||
|     } | ||||
|  | ||||
|     if input.key_pressed(VirtualKeyCode::X) { | ||||
|         state.east.update(true, irq); | ||||
|     } else if input.key_released(VirtualKeyCode::X) { | ||||
|     } | ||||
|     if input.key_released(VirtualKeyCode::X) { | ||||
|         state.east.update(false, irq); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,21 +1,19 @@ | ||||
| pub use gui::Egui; | ||||
| pub use instruction::Cycle; | ||||
| pub use sound::AudioMPSC; | ||||
| pub use apu::gen::AudioSPSC; | ||||
| pub type Cycle = u64; | ||||
|  | ||||
| pub const GB_WIDTH: usize = 160; | ||||
| pub const GB_HEIGHT: usize = 144; | ||||
|  | ||||
| mod apu; | ||||
| mod bus; | ||||
| mod cartridge; | ||||
| mod cpu; | ||||
| pub mod emu; | ||||
| mod gui; | ||||
| mod high_ram; | ||||
| mod instruction; | ||||
| mod interrupt; | ||||
| mod joypad; | ||||
| mod ppu; | ||||
| mod serial; | ||||
| mod sound; | ||||
| mod timer; | ||||
| mod work_ram; | ||||
|   | ||||
							
								
								
									
										116
									
								
								src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										116
									
								
								src/main.rs
									
									
									
									
									
								
							| @@ -1,17 +1,20 @@ | ||||
| use std::convert::TryInto; | ||||
|  | ||||
| use anyhow::{anyhow, Result}; | ||||
| use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg}; | ||||
| use gb::{AudioMPSC, Cycle, Egui, GB_HEIGHT, GB_WIDTH}; | ||||
| use gb::emu::build::EmulatorBuilder; | ||||
| use gb::{AudioSPSC, Cycle, GB_HEIGHT, GB_WIDTH}; | ||||
| use gilrs::Gilrs; | ||||
| use pixels::{Pixels, SurfaceTexture}; | ||||
| use pixels::{PixelsBuilder, SurfaceTexture}; | ||||
| use rodio::{OutputStream, Sink}; | ||||
| use std::time::Instant; | ||||
| use winit::dpi::LogicalSize; | ||||
| use winit::dpi::{LogicalSize, PhysicalSize}; | ||||
| use winit::event::{Event, VirtualKeyCode}; | ||||
| use winit::event_loop::{ControlFlow, EventLoop}; | ||||
| use winit::window::{Window, WindowBuilder}; | ||||
| use winit_input_helper::WinitInputHelper; | ||||
|  | ||||
| const SCALE: f64 = 2.0; | ||||
| const WINDOW_SCALE: usize = 3; | ||||
| const AUDIO_ENABLED: bool = false; | ||||
|  | ||||
| fn main() -> Result<()> { | ||||
|     let app = App::new(crate_name!()) | ||||
| @@ -38,68 +41,58 @@ fn main() -> Result<()> { | ||||
|         ) | ||||
|         .get_matches(); | ||||
|  | ||||
|     // `rom` is a required value in every situation so this will | ||||
|     // always exist. | ||||
|     let rom_path = m | ||||
|         .value_of("rom") | ||||
|         .expect("Required value 'rom' was provided"); | ||||
|     let mut emu_build = | ||||
|         EmulatorBuilder::new().with_cart(m.value_of("rom").expect("ROM path provided"))?; | ||||
|  | ||||
|     let mut game_boy = | ||||
|         gb::emu::init(m.value_of("boot"), rom_path).expect("Initialized DMG-01 Emulator"); | ||||
|     let cartridge_title = gb::emu::rom_title(&game_boy); | ||||
|     if let Some(path) = m.value_of("boot") { | ||||
|         emu_build = emu_build.with_boot(path)?; | ||||
|     } | ||||
|  | ||||
|     // Initialize Gamepad Support | ||||
|     let mut gamepad = Gilrs::new().expect("Initialized Gilrs for Controller Input"); | ||||
|     let mut emu = emu_build.finish(); | ||||
|     let rom_title = emu.title(); | ||||
|  | ||||
|     let mut gamepad = Gilrs::new().expect("Initialize Controller Support"); | ||||
|  | ||||
|     // Initialize GUI | ||||
|     let event_loop = EventLoop::new(); | ||||
|     let mut input = WinitInputHelper::new(); | ||||
|     let window = create_window(&event_loop, cartridge_title)?; | ||||
|     let window = create_window(&event_loop, rom_title)?; | ||||
|  | ||||
|     let (mut pixels, mut egui) = { | ||||
|     let mut pixels = { | ||||
|         let size = window.inner_size(); | ||||
|         let scale_factor = window.scale_factor(); | ||||
|         let surface_texture = SurfaceTexture::new(size.width, size.height, &window); | ||||
|         let pixels = Pixels::new(GB_WIDTH as u32, GB_HEIGHT as u32, surface_texture)?; | ||||
|         let egui = Egui::new(size.width, size.height, scale_factor, pixels.context()); | ||||
|  | ||||
|         (pixels, egui) | ||||
|         PixelsBuilder::new(GB_WIDTH as u32, GB_HEIGHT as u32, surface_texture) | ||||
|             .enable_vsync(false) | ||||
|             .build()? | ||||
|     }; | ||||
|  | ||||
|     let (send, recv) = AudioMPSC::new(); | ||||
|     game_boy.set_audio_src(send); | ||||
|  | ||||
|     // Initialize Audio | ||||
|     let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio"); | ||||
|     let sink = Sink::try_new(&stream_handle).expect("Initialize Audio Sink"); | ||||
|  | ||||
|     if AUDIO_ENABLED { | ||||
|         let spsc: AudioSPSC<f32> = Default::default(); | ||||
|         let (prod, cons) = spsc.init(); | ||||
|         let sink = { | ||||
|             let s = Sink::try_new(&stream_handle)?; | ||||
|             s.append(cons); | ||||
|             s.set_volume(0.1); | ||||
|             s | ||||
|         }; | ||||
|  | ||||
|         emu.set_prod(prod); | ||||
|  | ||||
|         std::thread::spawn(move || { | ||||
|         sink.append(recv); | ||||
|  | ||||
|             sink.sleep_until_end(); | ||||
|         }); | ||||
|     } | ||||
|  | ||||
|     let mut now = Instant::now(); | ||||
|     let mut cycle_count: Cycle = Default::default(); | ||||
|  | ||||
|     event_loop.run(move |event, _, control_flow| { | ||||
|         // Update egui | ||||
|         egui.handle_event(&event); | ||||
|  | ||||
|         if let Event::RedrawRequested(_) = event { | ||||
|             // Prepare egui | ||||
|             egui.prepare(&game_boy); | ||||
|  | ||||
|             // Render everything together | ||||
|             let render_result = pixels.render_with(|encoder, target, ctx| { | ||||
|                 // Render the texture | ||||
|                 ctx.scaling_renderer.render(encoder, target); | ||||
|  | ||||
|                 // Render egui | ||||
|                 egui.render(encoder, target, ctx); | ||||
|             }); | ||||
|  | ||||
|             if render_result | ||||
|             if pixels | ||||
|                 .render() | ||||
|                 .map_err(|e| anyhow!("pixels.render() failed: {}", e)) | ||||
|                 .is_err() | ||||
|             { | ||||
| @@ -114,26 +107,21 @@ fn main() -> Result<()> { | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             if let Some(scale_factor) = input.scale_factor() { | ||||
|                 egui.scale_factor(scale_factor); | ||||
|             } | ||||
|  | ||||
|             if let Some(size) = input.window_resized() { | ||||
|                 pixels.resize_surface(size.width, size.height); | ||||
|                 egui.resize(size.width, size.height); | ||||
|             } | ||||
|  | ||||
|             let delta = now.elapsed().subsec_nanos(); | ||||
|             now = Instant::now(); | ||||
|  | ||||
|             let pending = Cycle::new(delta / gb::emu::SM83_CYCLE_TIME.subsec_nanos()); | ||||
|             cycle_count += gb::emu::run(&mut game_boy, &mut gamepad, &input, pending); | ||||
|             cycle_count += gb::emu::run_frame(&mut emu, &mut gamepad, &input); | ||||
|  | ||||
|             if cycle_count >= gb::emu::CYCLES_IN_FRAME { | ||||
|                 // Draw Frame | ||||
|                 cycle_count = Cycle::new(0); | ||||
|                 cycle_count %= gb::emu::CYCLES_IN_FRAME; | ||||
|  | ||||
|                 gb::emu::draw(game_boy.ppu(), pixels.get_frame()); | ||||
|                 let buf: &mut [u8; GB_WIDTH * GB_HEIGHT * 4] = pixels | ||||
|                     .get_frame() | ||||
|                     .try_into() | ||||
|                     .expect("Size of Pixel Buffer is GB_WIDTH * GB_HEIGHT * 4"); | ||||
|  | ||||
|                 gb::emu::draw_frame(&emu, buf); | ||||
|                 window.request_redraw(); | ||||
|             } | ||||
|         } | ||||
| @@ -141,14 +129,16 @@ fn main() -> Result<()> { | ||||
| } | ||||
|  | ||||
| fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> { | ||||
|     let size = LogicalSize::new((GB_WIDTH as f64) * SCALE, (GB_HEIGHT as f64) * SCALE); | ||||
|     let logical = LogicalSize::new(GB_WIDTH as f64, GB_HEIGHT as f64); | ||||
|     let physical = PhysicalSize::new( | ||||
|         (GB_WIDTH * WINDOW_SCALE) as f32, | ||||
|         (GB_HEIGHT * WINDOW_SCALE) as f32, | ||||
|     ); | ||||
|  | ||||
|     Ok(WindowBuilder::new() | ||||
|         .with_title(title) | ||||
|         .with_inner_size(size) | ||||
|         .with_min_inner_size(size) | ||||
|         .with_min_inner_size(logical) | ||||
|         .with_inner_size(physical) | ||||
|         .with_resizable(true) | ||||
|         .with_decorations(true) | ||||
|         .with_transparent(false) | ||||
|         // .with_drag_and_drop(false) // OleInitialize failed error if this is set to true | ||||
|         .build(event_loop)?) | ||||
| } | ||||
|   | ||||
							
								
								
									
										338
									
								
								src/ppu.rs
									
									
									
									
									
								
							
							
						
						
									
										338
									
								
								src/ppu.rs
									
									
									
									
									
								
							| @@ -1,5 +1,5 @@ | ||||
| use crate::bus::BusIo; | ||||
| use crate::instruction::Cycle; | ||||
| use crate::Cycle; | ||||
| use crate::GB_HEIGHT; | ||||
| use crate::GB_WIDTH; | ||||
| use dma::DirectMemoryAccess; | ||||
| @@ -8,7 +8,7 @@ use std::convert::TryInto; | ||||
| pub(crate) use types::PpuMode; | ||||
| use types::{ | ||||
|     BackgroundPalette, GrayShade, LCDControl, LCDStatus, ObjectFlags, ObjectPalette, | ||||
|     ObjectPaletteId, ObjectSize, Pixels, RenderPriority, TileDataAddress, | ||||
|     ObjectPaletteKind, ObjectSize, Pixels, RenderPriority, TileDataAddress, | ||||
| }; | ||||
|  | ||||
| mod dma; | ||||
| @@ -33,7 +33,7 @@ const LIGHT_GRAY: [u8; 4] = 0xAEBA89FFu32.to_be_bytes(); | ||||
| const DARK_GRAY: [u8; 4] = 0x5E6745FFu32.to_be_bytes(); | ||||
| const BLACK: [u8; 4] = 0x202020FFu32.to_be_bytes(); | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| #[derive(Debug)] | ||||
| pub struct Ppu { | ||||
|     pub(crate) int: Interrupt, | ||||
|     /// 0xFF40 | LCDC - LCD Control | ||||
| @@ -47,10 +47,14 @@ pub struct Ppu { | ||||
|     pub(crate) dma: DirectMemoryAccess, | ||||
|     scan_state: OamScanState, | ||||
|     fetch: PixelFetcher, | ||||
|     fifo: FifoRenderer, | ||||
|     fifo: PixelFifo, | ||||
|     obj_buffer: ObjectBuffer, | ||||
|     frame_buf: Box<[u8; GB_WIDTH * GB_HEIGHT * 4]>, | ||||
|     window_stat: WindowStatus, | ||||
|  | ||||
|     scanline_start: bool, | ||||
|     to_discard: u8, | ||||
|  | ||||
|     x_pos: u8, | ||||
|     cycle: Cycle, | ||||
| } | ||||
| @@ -66,7 +70,7 @@ impl BusIo for Ppu { | ||||
| } | ||||
|  | ||||
| impl Ppu { | ||||
|     pub(crate) fn clock(&mut self) { | ||||
|     pub(crate) fn tick(&mut self) { | ||||
|         self.cycle += 1; | ||||
|  | ||||
|         if !self.ctrl.lcd_enabled() { | ||||
| @@ -75,7 +79,7 @@ impl Ppu { | ||||
|  | ||||
|         match self.stat.mode() { | ||||
|             PpuMode::OamScan => { | ||||
|                 if self.cycle >= 80.into() { | ||||
|                 if self.cycle >= 80 { | ||||
|                     self.stat.set_mode(PpuMode::Drawing); | ||||
|                 } | ||||
|  | ||||
| @@ -84,7 +88,7 @@ impl Ppu { | ||||
|             PpuMode::Drawing => { | ||||
|                 if self.ctrl.lcd_enabled() { | ||||
|                     // Only Draw when the LCD Is Enabled | ||||
|                     self.draw(self.cycle.into()); | ||||
|                     self.draw(self.cycle); | ||||
|                 } else { | ||||
|                     self.reset(); | ||||
|                 } | ||||
| @@ -105,6 +109,8 @@ impl Ppu { | ||||
|                     } | ||||
|  | ||||
|                     self.x_pos = 0; | ||||
|                     self.scanline_start = true; | ||||
|                     self.to_discard = 0; | ||||
|  | ||||
|                     self.fetch.hblank_reset(); | ||||
|                     self.window_stat.hblank_reset(); | ||||
| @@ -119,7 +125,7 @@ impl Ppu { | ||||
|             PpuMode::HBlank => { | ||||
|                 // This mode will always end at 456 cycles | ||||
|  | ||||
|                 if self.cycle >= 456.into() { | ||||
|                 if self.cycle >= 456 { | ||||
|                     self.cycle %= 456; | ||||
|                     self.pos.line_y += 1; | ||||
|  | ||||
| @@ -161,7 +167,7 @@ impl Ppu { | ||||
|                 } | ||||
|             } | ||||
|             PpuMode::VBlank => { | ||||
|                 if self.cycle > 456.into() { | ||||
|                 if self.cycle > 456 { | ||||
|                     self.cycle %= 456; | ||||
|                     self.pos.line_y += 1; | ||||
|  | ||||
| @@ -222,23 +228,26 @@ impl Ppu { | ||||
|         self.scan_state.next(); | ||||
|     } | ||||
|  | ||||
|     fn draw(&mut self, _cycle: u32) { | ||||
|     fn draw(&mut self, _cycle: Cycle) { | ||||
|         use FetcherState::*; | ||||
|  | ||||
|         let iter = &mut self.obj_buffer.iter(); | ||||
|         let mut iter = self.obj_buffer.iter_mut(); | ||||
|         let default = &mut None; | ||||
|  | ||||
|         let obj_attr = loop { | ||||
|             match iter.flatten().next() { | ||||
|                 Some(attr) => { | ||||
|             match iter.next() { | ||||
|                 Some(attr_opt) => { | ||||
|                     if let Some(attr) = attr_opt { | ||||
|                         if attr.x <= (self.x_pos + 8) { | ||||
|                             self.fetch.back.reset(); | ||||
|                             self.fetch.back.pause(); | ||||
|                             self.fifo.pause(); | ||||
|  | ||||
|                         break Some(*attr); | ||||
|                             break attr_opt; | ||||
|                         } | ||||
|                     } | ||||
|                 None => break None, | ||||
|                 } | ||||
|                 None => break default, | ||||
|             } | ||||
|         }; | ||||
|  | ||||
| @@ -246,10 +255,10 @@ impl Ppu { | ||||
|             match self.fetch.obj.state { | ||||
|                 TileNumber => { | ||||
|                     self.fetch.obj.tile.with_id(attr.tile_index); | ||||
|                     self.fetch.obj.next(ToLowByteSleep); | ||||
|                     self.fetch.obj.next(SleepOne); | ||||
|                 } | ||||
|                 ToLowByteSleep => self.fetch.obj.next(TileLowByte), | ||||
|                 TileLowByte => { | ||||
|                 SleepOne => self.fetch.obj.next(TileLow), | ||||
|                 TileLow => { | ||||
|                     let obj_size = self.ctrl.obj_size(); | ||||
|  | ||||
|                     let addr = PixelFetcher::get_obj_addr(&attr, &self.pos, obj_size); | ||||
| @@ -257,10 +266,10 @@ impl Ppu { | ||||
|                     let byte = self.read_byte(addr); | ||||
|                     self.fetch.obj.tile.with_low_byte(byte); | ||||
|  | ||||
|                     self.fetch.obj.next(ToHighByteSleep); | ||||
|                     self.fetch.obj.next(SleepTwo); | ||||
|                 } | ||||
|                 ToHighByteSleep => self.fetch.obj.next(TileHighByte), | ||||
|                 TileHighByte => { | ||||
|                 SleepTwo => self.fetch.obj.next(TileHigh), | ||||
|                 TileHigh => { | ||||
|                     let obj_size = self.ctrl.obj_size(); | ||||
|  | ||||
|                     let addr = PixelFetcher::get_obj_addr(&attr, &self.pos, obj_size); | ||||
| @@ -268,10 +277,10 @@ impl Ppu { | ||||
|                     let byte = self.read_byte(addr + 1); | ||||
|                     self.fetch.obj.tile.with_high_byte(byte); | ||||
|  | ||||
|                     self.fetch.obj.next(ToFifoSleep); | ||||
|                     self.fetch.obj.next(SleepThree); | ||||
|                 } | ||||
|                 ToFifoSleep => self.fetch.obj.next(SendToFifoOne), | ||||
|                 SendToFifoOne => { | ||||
|                 SleepThree => self.fetch.obj.next(ToFifoOne), | ||||
|                 ToFifoOne => { | ||||
|                     // Load into Fifo | ||||
|                     let (high, low) = self | ||||
|                         .fetch | ||||
| @@ -282,25 +291,21 @@ impl Ppu { | ||||
|  | ||||
|                     let tbpp = Pixels::from_bytes(high, low); | ||||
|  | ||||
|                     let palette = match attr.flags.palette() { | ||||
|                         ObjectPaletteId::Zero => self.monochrome.obj_palette_0, | ||||
|                         ObjectPaletteId::One => self.monochrome.obj_palette_1, | ||||
|                     }; | ||||
|  | ||||
|                     let end = Pixels::PIXEL_COUNT - self.fifo.obj.len(); | ||||
|                     let start = Pixels::PIXEL_COUNT - end; | ||||
|  | ||||
|                     let palette_kind = attr.flags.palette(); | ||||
|                     let x_flip = attr.flags.x_flip(); | ||||
|  | ||||
|                     for i in start..Pixels::PIXEL_COUNT { | ||||
|                     let pixel_count = (attr.x - self.x_pos) as usize; | ||||
|                     let start = self.fifo.obj.len(); | ||||
|  | ||||
|                     for i in start..pixel_count { | ||||
|                         let x = if x_flip { 7 - i } else { i }; | ||||
|  | ||||
|                         let priority = attr.flags.priority(); | ||||
|                         let shade = palette.shade(tbpp.shade_id(x)); | ||||
|                         let shade_id = tbpp.shade_id(x); | ||||
|  | ||||
|                         let fifo_info = ObjectFifoInfo { | ||||
|                             shade, | ||||
|                             palette, | ||||
|                         let fifo_info = ObjPixelProperty { | ||||
|                             shade_id, | ||||
|                             palette_kind, | ||||
|                             priority, | ||||
|                         }; | ||||
|  | ||||
| @@ -309,11 +314,11 @@ impl Ppu { | ||||
|  | ||||
|                     self.fetch.back.resume(); | ||||
|                     self.fifo.resume(); | ||||
|                     self.obj_buffer.remove(&attr); | ||||
|                     let _ = std::mem::take(obj_attr); | ||||
|  | ||||
|                     self.fetch.obj.next(SendToFifoTwo); | ||||
|                     self.fetch.obj.next(ToFifoTwo); | ||||
|                 } | ||||
|                 SendToFifoTwo => self.fetch.obj.reset(), | ||||
|                 ToFifoTwo => self.fetch.obj.reset(), | ||||
|             } | ||||
|         } | ||||
|  | ||||
| @@ -343,86 +348,61 @@ impl Ppu { | ||||
|                     self.fetch.back.tile.with_id(id); | ||||
|  | ||||
|                     // Move on to the Next state in 2 T-cycles | ||||
|                     self.fetch.back.next(ToLowByteSleep); | ||||
|                     self.fetch.back.next(SleepOne); | ||||
|                 } | ||||
|                 ToLowByteSleep => self.fetch.back.next(TileLowByte), | ||||
|                 TileLowByte => { | ||||
|                 SleepOne => self.fetch.back.next(TileLow), | ||||
|                 TileLow => { | ||||
|                     let addr = self.fetch.bg_byte_addr(&self.ctrl, &self.pos); | ||||
|  | ||||
|                     let low = self.read_byte(addr); | ||||
|                     self.fetch.back.tile.with_low_byte(low); | ||||
|  | ||||
|                     self.fetch.back.next(ToHighByteSleep); | ||||
|                     self.fetch.back.next(SleepTwo); | ||||
|                 } | ||||
|                 ToHighByteSleep => self.fetch.back.next(TileHighByte), | ||||
|                 TileHighByte => { | ||||
|                 SleepTwo => self.fetch.back.next(TileHigh), | ||||
|                 TileHigh => { | ||||
|                     let addr = self.fetch.bg_byte_addr(&self.ctrl, &self.pos); | ||||
|  | ||||
|                     let high = self.read_byte(addr + 1); | ||||
|                     self.fetch.back.tile.with_high_byte(high); | ||||
|  | ||||
|                     self.fetch.back.next(ToFifoSleep); | ||||
|                     self.fetch.back.next(SleepThree); | ||||
|                 } | ||||
|                 ToFifoSleep => self.fetch.back.next(SendToFifoOne), | ||||
|                 SendToFifoOne => { | ||||
|                     self.fetch.back.next(SendToFifoTwo); | ||||
|                 SleepThree => self.fetch.back.next(ToFifoOne), | ||||
|                 ToFifoOne => { | ||||
|                     self.fetch.back.next(ToFifoTwo); | ||||
|                 } | ||||
|                 SendToFifoTwo => { | ||||
|                     let palette = &self.monochrome.bg_palette; | ||||
|                     self.fetch.send_to_fifo(&mut self.fifo, palette); | ||||
|                 ToFifoTwo => { | ||||
|                     if let Ok(()) = self.fetch.send_to_fifo(&mut self.fifo) { | ||||
|                         self.fetch.x_pos += 1; | ||||
|  | ||||
|                         self.fetch.back.next(TileNumber); | ||||
|                         self.fetch.back.tile = Default::default(); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if self.fifo.is_enabled() { | ||||
|             use RenderPriority::*; | ||||
|  | ||||
|             if self.x_pos == 0 && !self.fifo.back.is_empty() { | ||||
|                 let to_discard = self.pos.scroll_x % 8; | ||||
|  | ||||
|                 for _ in 0..to_discard { | ||||
|                     let _ = self.fifo.back.pop_front(); | ||||
|             if self.x_pos == 0 && self.scanline_start { | ||||
|                 self.to_discard = self.pos.scroll_x % 8; | ||||
|                 self.scanline_start = false; | ||||
|             } | ||||
|  | ||||
|             if self.to_discard > 0 && !self.fifo.back.is_empty() { | ||||
|                 let _ = self.fifo.back.pop_front(); | ||||
|                 self.to_discard -= 1; | ||||
|  | ||||
|                 // Delay the PPU by one cycle | ||||
|                 return; | ||||
|             } | ||||
|  | ||||
|             // Handle Background Pixel and Sprite FIFO | ||||
|             let bg_enabled = self.ctrl.bg_win_enabled(); | ||||
|             let obj_enabled = self.ctrl.obj_enabled(); | ||||
|             let i0_colour = self.monochrome.bg_palette.i0_colour(); | ||||
|  | ||||
|             // FIXME: Is this the correct behaviour | ||||
|             let rgba_opt = self.fifo.back.pop_front().map(|bg_info| { | ||||
|                 let bg_shade = if bg_enabled { bg_info.shade } else { i0_colour }; | ||||
|  | ||||
|                 match self.fifo.obj.pop_front() { | ||||
|                     Some(obj_info) => { | ||||
|                         if obj_enabled { | ||||
|                             match (obj_info.shade, obj_info.priority) { | ||||
|                                 (Some(obj_shade), BackgroundAndWindow) => match bg_info.shade { | ||||
|                                     GrayShade::White => obj_shade.into_rgba(), | ||||
|                                     _ => bg_shade.into_rgba(), | ||||
|                                 }, | ||||
|                                 (Some(obj_shade), Object) => obj_shade.into_rgba(), | ||||
|                                 (None, _) => bg_shade.into_rgba(), | ||||
|                             } | ||||
|                         } else { | ||||
|                             bg_shade.into_rgba() | ||||
|                         } | ||||
|                     } | ||||
|                     None => bg_shade.into_rgba(), | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             if let Some(rgba) = rgba_opt.as_ref() { | ||||
|             if let Some(rgba) = self.clock_fifo().map(GrayShade::into_rgba) { | ||||
|                 let y = self.pos.line_y as usize; | ||||
|                 let x = self.x_pos as usize; | ||||
|  | ||||
|                 let i = (GB_WIDTH * 4) * y + (x * 4); | ||||
|                 self.frame_buf[i..(i + rgba.len())].copy_from_slice(rgba); | ||||
|                 self.frame_buf[i..(i + rgba.len())].copy_from_slice(&rgba); | ||||
|  | ||||
|                 self.x_pos += 1; | ||||
|             } | ||||
| @@ -438,8 +418,51 @@ impl Ppu { | ||||
|         self.frame_buf.swap_with_slice(&mut blank); | ||||
|     } | ||||
|  | ||||
|     pub fn copy_to_gui(&self, frame: &mut [u8]) { | ||||
|         frame.copy_from_slice(self.frame_buf.as_ref()); | ||||
|     pub(crate) fn frame_buf(&self) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] { | ||||
|         &self.frame_buf | ||||
|     } | ||||
|  | ||||
|     fn clock_fifo(&mut self) -> Option<GrayShade> { | ||||
|         use ObjectPaletteKind::*; | ||||
|         use RenderPriority::*; | ||||
|  | ||||
|         let obj_palette_0 = &self.monochrome.obj_palette_0; | ||||
|         let obj_palette_1 = &self.monochrome.obj_palette_1; | ||||
|  | ||||
|         match self.fifo.back.pop_front() { | ||||
|             Some(bg_pixel) => match self.fifo.obj.pop_front() { | ||||
|                 Some(obj_pixel) if self.ctrl.obj_enabled() => match obj_pixel.priority { | ||||
|                     Object | BackgroundAndWindow if obj_pixel.shade_id == 0 => { | ||||
|                         Some(self.bg_pixel(bg_pixel.shade_id)) | ||||
|                     } | ||||
|                     BackgroundAndWindow if bg_pixel.shade_id != 0 => { | ||||
|                         Some(self.bg_pixel(bg_pixel.shade_id)) | ||||
|                     } | ||||
|                     Object | BackgroundAndWindow => { | ||||
|                         let maybe_sprite = match obj_pixel.palette_kind { | ||||
|                             Zero => obj_palette_0.shade(obj_pixel.shade_id), | ||||
|                             One => obj_palette_1.shade(obj_pixel.shade_id), | ||||
|                         }; | ||||
|  | ||||
|                         let sprite = maybe_sprite | ||||
|                             .expect("Sprite w/ a colour id of 0 has already been handled"); | ||||
|                         Some(sprite) | ||||
|                     } | ||||
|                 }, | ||||
|                 _ => Some(self.bg_pixel(bg_pixel.shade_id)), | ||||
|             }, | ||||
|             None => None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn bg_pixel(&self, shade_id: u8) -> GrayShade { | ||||
|         let bg_palette = &self.monochrome.bg_palette; | ||||
|  | ||||
|         if self.ctrl.bg_win_enabled() { | ||||
|             bg_palette.shade(shade_id) | ||||
|         } else { | ||||
|             bg_palette.shade(0) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -447,7 +470,7 @@ impl Default for Ppu { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             vram: Box::new([0u8; VRAM_SIZE]), | ||||
|             cycle: Cycle::new(0), | ||||
|             cycle: Default::default(), | ||||
|             frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]), | ||||
|             int: Default::default(), | ||||
|             ctrl: Default::default(), | ||||
| @@ -462,11 +485,13 @@ impl Default for Ppu { | ||||
|             window_stat: Default::default(), | ||||
|             dma: Default::default(), | ||||
|             x_pos: 0, | ||||
|             scanline_start: true, | ||||
|             to_discard: Default::default(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, Default)] | ||||
| #[derive(Debug, Default)] | ||||
| pub(crate) struct Interrupt { | ||||
|     _vblank: bool, | ||||
|     _lcd_stat: bool, | ||||
| @@ -490,7 +515,7 @@ impl Interrupt { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, Default)] | ||||
| #[derive(Debug, Default)] | ||||
| pub(crate) struct ScreenPosition { | ||||
|     /// 0xFF42 | SCY - Scroll Y | ||||
|     pub(crate) scroll_y: u8, | ||||
| @@ -506,7 +531,7 @@ pub(crate) struct ScreenPosition { | ||||
|     pub(crate) window_x: u8, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, Default)] | ||||
| #[derive(Debug, Default)] | ||||
| pub(crate) struct Monochrome { | ||||
|     /// 0xFF47 | BGP - Background Palette Data | ||||
|     pub(crate) bg_palette: BackgroundPalette, | ||||
| @@ -516,7 +541,7 @@ pub(crate) struct Monochrome { | ||||
|     pub(crate) obj_palette_1: ObjectPalette, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct ObjectAttributeTable { | ||||
|     buf: Box<[u8; OAM_SIZE]>, | ||||
| } | ||||
| @@ -583,75 +608,42 @@ impl<'a> From<&'a [u8; 4]> for ObjectAttribute { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| #[derive(Debug)] | ||||
| struct ObjectBuffer { | ||||
|     buf: [Option<ObjectAttribute>; OBJECT_LIMIT], | ||||
|     inner: [Option<ObjectAttribute>; OBJECT_LIMIT], | ||||
|     len: usize, | ||||
| } | ||||
|  | ||||
| impl ObjectBuffer { | ||||
|     fn iter(&self) -> std::slice::Iter<'_, Option<ObjectAttribute>> { | ||||
|         self.into_iter() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> IntoIterator for &'a ObjectBuffer { | ||||
|     type Item = &'a Option<ObjectAttribute>; | ||||
|  | ||||
|     type IntoIter = std::slice::Iter<'a, Option<ObjectAttribute>>; | ||||
|  | ||||
|     fn into_iter(self) -> Self::IntoIter { | ||||
|         self.buf.iter() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<'a> IntoIterator for &'a mut ObjectBuffer { | ||||
|     type Item = &'a Option<ObjectAttribute>; | ||||
|  | ||||
|     type IntoIter = std::slice::Iter<'a, Option<ObjectAttribute>>; | ||||
|  | ||||
|     fn into_iter(self) -> Self::IntoIter { | ||||
|         self.buf.iter() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl ObjectBuffer { | ||||
|     fn is_full(&self) -> bool { | ||||
|         self.len == OBJECT_LIMIT | ||||
|     } | ||||
|  | ||||
|     fn clear(&mut self) { | ||||
|         self.buf = [Default::default(); 10]; | ||||
|         self.inner = [Default::default(); 10]; | ||||
|         self.len = 0; | ||||
|     } | ||||
|  | ||||
|     fn add(&mut self, attr: ObjectAttribute) { | ||||
|         self.buf[self.len] = Some(attr); | ||||
|         self.inner[self.len] = Some(attr); | ||||
|         self.len += 1; | ||||
|     } | ||||
|  | ||||
|     fn remove(&mut self, attr: &ObjectAttribute) { | ||||
|         let maybe_index = self.buf.iter().position(|maybe_attr| match maybe_attr { | ||||
|             Some(other_attr) => attr == other_attr, | ||||
|             None => false, | ||||
|         }); | ||||
|  | ||||
|         if let Some(i) = maybe_index { | ||||
|             self.buf[i] = None; | ||||
|         } | ||||
|     fn iter_mut(&mut self) -> std::slice::IterMut<'_, Option<ObjectAttribute>> { | ||||
|         self.inner.iter_mut() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for ObjectBuffer { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             buf: [Default::default(); OBJECT_LIMIT], | ||||
|             inner: [Default::default(); OBJECT_LIMIT], | ||||
|             len: Default::default(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, Default)] | ||||
| #[derive(Debug, Default)] | ||||
| struct PixelFetcher { | ||||
|     x_pos: u8, | ||||
|     back: BackgroundFetcher, | ||||
| @@ -720,7 +712,11 @@ impl PixelFetcher { | ||||
|         tile_data_addr + (offset * 2) | ||||
|     } | ||||
|  | ||||
|     fn send_to_fifo(&self, fifo: &mut FifoRenderer, palette: &BackgroundPalette) { | ||||
|     fn send_to_fifo(&self, fifo: &mut PixelFifo) -> Result<(), ()> { | ||||
|         if !fifo.back.is_empty() { | ||||
|             return Err(()); | ||||
|         } | ||||
|  | ||||
|         let (high, low) = self | ||||
|             .back | ||||
|             .tile | ||||
| @@ -729,14 +725,14 @@ impl PixelFetcher { | ||||
|  | ||||
|         let tbpp = Pixels::from_bytes(high, low); | ||||
|  | ||||
|         if fifo.back.is_empty() { | ||||
|         for x in 0..Pixels::PIXEL_COUNT { | ||||
|                 let shade = palette.shade(tbpp.shade_id(x)); | ||||
|             let shade_id = tbpp.shade_id(x); | ||||
|  | ||||
|                 let fifo_info = BackgroundFifoInfo { shade }; | ||||
|             let fifo_info = BgPixelProperty { shade_id }; | ||||
|             fifo.back.push_back(fifo_info); | ||||
|         } | ||||
|         } | ||||
|  | ||||
|         Ok(()) | ||||
|     } | ||||
|  | ||||
|     fn get_obj_addr(attr: &ObjectAttribute, pos: &ScreenPosition, size: ObjectSize) -> u16 { | ||||
| @@ -766,7 +762,7 @@ trait Fetcher { | ||||
|     fn hblank_reset(&mut self); | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| #[derive(Debug)] | ||||
| struct BackgroundFetcher { | ||||
|     state: FetcherState, | ||||
|     tile: TileBuilder, | ||||
| @@ -832,7 +828,7 @@ impl Default for BackgroundFetcher { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, Default)] | ||||
| #[derive(Debug, Default)] | ||||
| struct ObjectFetcher { | ||||
|     state: FetcherState, | ||||
|     tile: TileBuilder, | ||||
| @@ -854,7 +850,7 @@ impl Fetcher for ObjectFetcher { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, Default)] | ||||
| #[derive(Debug, Default)] | ||||
| struct WindowLineCounter { | ||||
|     count: u8, | ||||
| } | ||||
| @@ -876,13 +872,13 @@ impl WindowLineCounter { | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| enum FetcherState { | ||||
|     TileNumber, | ||||
|     ToLowByteSleep, | ||||
|     TileLowByte, | ||||
|     ToHighByteSleep, | ||||
|     TileHighByte, | ||||
|     ToFifoSleep, | ||||
|     SendToFifoOne, | ||||
|     SendToFifoTwo, | ||||
|     SleepOne, | ||||
|     TileLow, | ||||
|     SleepTwo, | ||||
|     TileHigh, | ||||
|     SleepThree, | ||||
|     ToFifoOne, | ||||
|     ToFifoTwo, | ||||
| } | ||||
|  | ||||
| impl Default for FetcherState { | ||||
| @@ -891,28 +887,28 @@ impl Default for FetcherState { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, Default)] | ||||
| struct BackgroundFifoInfo { | ||||
|     shade: GrayShade, | ||||
| #[derive(Debug, Default)] | ||||
| struct BgPixelProperty { | ||||
|     shade_id: u8, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, Default)] | ||||
| struct ObjectFifoInfo { | ||||
|     shade: Option<GrayShade>, | ||||
|     palette: ObjectPalette, | ||||
| #[derive(Debug, Default)] | ||||
| struct ObjPixelProperty { | ||||
|     shade_id: u8, | ||||
|     palette_kind: ObjectPaletteKind, | ||||
|     priority: RenderPriority, | ||||
| } | ||||
|  | ||||
| // FIXME: Fifo Registers have a known size. Are heap allocations | ||||
| // really necessary here? | ||||
| #[derive(Debug, Clone)] | ||||
| struct FifoRenderer { | ||||
|     back: VecDeque<BackgroundFifoInfo>, | ||||
|     obj: VecDeque<ObjectFifoInfo>, | ||||
| #[derive(Debug)] | ||||
| struct PixelFifo { | ||||
|     back: VecDeque<BgPixelProperty>, | ||||
|     obj: VecDeque<ObjPixelProperty>, | ||||
|     enabled: bool, | ||||
| } | ||||
|  | ||||
| impl FifoRenderer { | ||||
| impl PixelFifo { | ||||
|     fn is_enabled(&self) -> bool { | ||||
|         self.enabled | ||||
|     } | ||||
| @@ -926,7 +922,7 @@ impl FifoRenderer { | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for FifoRenderer { | ||||
| impl Default for PixelFifo { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             back: VecDeque::with_capacity(8), | ||||
| @@ -936,7 +932,7 @@ impl Default for FifoRenderer { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, Default)] | ||||
| #[derive(Debug, Default)] | ||||
| struct TileBuilder { | ||||
|     id: Option<u8>, | ||||
|     low: Option<u8>, | ||||
| @@ -961,7 +957,7 @@ impl TileBuilder { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, Default)] | ||||
| #[derive(Debug, Default)] | ||||
| struct OamScanState { | ||||
|     count: u8, | ||||
|     mode: OamScanMode, | ||||
| @@ -982,8 +978,8 @@ impl OamScanState { | ||||
|         self.count | ||||
|     } | ||||
|  | ||||
|     fn mode(&self) -> OamScanMode { | ||||
|         self.mode | ||||
|     fn mode(&self) -> &OamScanMode { | ||||
|         &self.mode | ||||
|     } | ||||
|  | ||||
|     fn next(&mut self) { | ||||
| @@ -1008,7 +1004,7 @@ impl Default for OamScanMode { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, Default)] | ||||
| #[derive(Debug, Default)] | ||||
| struct WindowStatus { | ||||
|     /// This will be true if WY == LY at any point in the frame thus far | ||||
|     coincidence: bool, | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| use crate::instruction::Cycle; | ||||
| use crate::Cycle; | ||||
|  | ||||
| #[derive(Debug, Default, Clone)] | ||||
| #[derive(Debug, Default)] | ||||
| pub(crate) struct DirectMemoryAccess { | ||||
|     pub(crate) state: DmaState, | ||||
|     cycle: Cycle, | ||||
| @@ -9,7 +9,7 @@ pub(crate) struct DirectMemoryAccess { | ||||
| } | ||||
|  | ||||
| impl DirectMemoryAccess { | ||||
|     pub(crate) fn clock(&mut self) -> Option<(u16, u16)> { | ||||
|     pub(crate) fn tick(&mut self) -> Option<(u16, u16)> { | ||||
|         match self.state { | ||||
|             DmaState::Pending => { | ||||
|                 self.cycle += 1; | ||||
| @@ -28,7 +28,7 @@ impl DirectMemoryAccess { | ||||
|  | ||||
|                 let src_addr = self | ||||
|                     .start | ||||
|                     .addr | ||||
|                     .0 | ||||
|                     .as_mut() | ||||
|                     .expect("Source Address present during DMA Transfer"); | ||||
|  | ||||
| @@ -56,9 +56,9 @@ impl DirectMemoryAccess { | ||||
|     } | ||||
|  | ||||
|     fn reset(&mut self) { | ||||
|         self.cycle = Cycle::new(0); | ||||
|         self.cycle = 0; | ||||
|         self.state = DmaState::Disabled; | ||||
|         self.start.addr = None; | ||||
|         self.start.0 = None; | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -75,26 +75,21 @@ impl Default for DmaState { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Default, Clone, Copy)] | ||||
| pub(crate) struct DmaAddress { | ||||
|     /// The current *source* address of the DMA Transfer | ||||
|     /// | ||||
|     /// NB: Will be None if no DMA Transfer is in progress | ||||
|     addr: Option<u16>, | ||||
| } | ||||
| #[derive(Debug, Clone, Copy, Default)] | ||||
| pub(crate) struct DmaAddress(Option<u16>); | ||||
|  | ||||
| impl DmaAddress { | ||||
|     pub(crate) fn update(&mut self, byte: u8, state: &mut DmaState) { | ||||
|         let start = (byte as u16) << 8; | ||||
|  | ||||
|         self.addr = Some(start); | ||||
|         self.0 = Some(start); | ||||
|         *state = DmaState::Pending; | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<DmaAddress> for u8 { | ||||
|     fn from(ctrl: DmaAddress) -> Self { | ||||
|         match ctrl.addr { | ||||
|         match ctrl.0 { | ||||
|             Some(addr) => (addr >> 8) as u8, | ||||
|             None => 0xFF, // TODO: What garbage value should be here? | ||||
|         } | ||||
|   | ||||
| @@ -332,7 +332,7 @@ bitfield! { | ||||
|     pub from into RenderPriority, priority, set_priority: 7, 7; | ||||
|     pub y_flip, set_y_flip: 6; | ||||
|     pub x_flip, set_x_flip: 5; | ||||
|     pub from into ObjectPaletteId, palette, set_palette: 4, 4; | ||||
|     pub from into ObjectPaletteKind, palette, set_palette: 4, 4; | ||||
| } | ||||
|  | ||||
| impl Eq for ObjectFlags {} | ||||
| @@ -368,23 +368,29 @@ impl Default for ObjectFlags { | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| pub enum ObjectPaletteId { | ||||
| pub enum ObjectPaletteKind { | ||||
|     Zero = 0, | ||||
|     One = 1, | ||||
| } | ||||
|  | ||||
| impl From<u8> for ObjectPaletteId { | ||||
| impl Default for ObjectPaletteKind { | ||||
|     fn default() -> Self { | ||||
|         Self::Zero | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<u8> for ObjectPaletteKind { | ||||
|     fn from(byte: u8) -> Self { | ||||
|         match byte & 0b01 { | ||||
|             0b00 => ObjectPaletteId::Zero, | ||||
|             0b01 => ObjectPaletteId::One, | ||||
|             0b00 => ObjectPaletteKind::Zero, | ||||
|             0b01 => ObjectPaletteKind::One, | ||||
|             _ => unreachable!("{:#04X} is not a valid value for BgPaletteNumber", byte), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<ObjectPaletteId> for u8 { | ||||
|     fn from(palette_num: ObjectPaletteId) -> Self { | ||||
| impl From<ObjectPaletteKind> for u8 { | ||||
|     fn from(palette_num: ObjectPaletteKind) -> Self { | ||||
|         palette_num as u8 | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										79
									
								
								src/scheduler.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/scheduler.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | ||||
| use crate::Cycle; | ||||
| use std::collections::BinaryHeap; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct Scheduler { | ||||
|     timestamp: Cycle, | ||||
|     queue: BinaryHeap<Event>, | ||||
| } | ||||
|  | ||||
| impl Scheduler { | ||||
|     pub(crate) fn init() -> Self { | ||||
|         let mut scheduler = Self { | ||||
|             timestamp: Default::default(), | ||||
|             queue: Default::default(), | ||||
|         }; | ||||
|  | ||||
|         scheduler.push(Event { | ||||
|             kind: EventKind::TimestampOverflow, | ||||
|             timestamp: Cycle::MAX, | ||||
|             cb: |_delay| panic!("Reached Cycle::MAX"), | ||||
|         }); | ||||
|  | ||||
|         scheduler | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn push(&mut self, event: Event) { | ||||
|         self.queue.push(event); | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn step(&mut self, cycles: Cycle) { | ||||
|         self.timestamp += cycles; | ||||
|  | ||||
|         loop { | ||||
|             let should_pop = match self.queue.peek() { | ||||
|                 Some(event) => self.timestamp >= event.timestamp, | ||||
|                 None => false, | ||||
|             }; | ||||
|  | ||||
|             if !should_pop { | ||||
|                 break; | ||||
|             } | ||||
|  | ||||
|             let event = self.queue.pop().expect("Pop Event from Scheduler Queue"); | ||||
|  | ||||
|             (event.cb)(self.timestamp - event.timestamp); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct Event { | ||||
|     kind: EventKind, | ||||
|     cb: fn(Cycle), | ||||
|     pub(crate) timestamp: Cycle, | ||||
| } | ||||
|  | ||||
| impl Eq for Event {} | ||||
| impl PartialEq for Event { | ||||
|     fn eq(&self, other: &Self) -> bool { | ||||
|         self.kind == other.kind && self.timestamp == other.timestamp | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl PartialOrd for Event { | ||||
|     fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { | ||||
|         Some(self.cmp(other)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Ord for Event { | ||||
|     fn cmp(&self, other: &Self) -> std::cmp::Ordering { | ||||
|         self.timestamp.cmp(&other.timestamp) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||
| pub(crate) enum EventKind { | ||||
|     TimestampOverflow, | ||||
| } | ||||
| @@ -1,6 +1,6 @@ | ||||
| use bitfield::bitfield; | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, Default)] | ||||
| #[derive(Debug, Default)] | ||||
| pub(crate) struct Serial { | ||||
|     /// 0xFF01 | SB - Serial Transfer Data | ||||
|     pub(crate) next: u8, | ||||
|   | ||||
							
								
								
									
										1235
									
								
								src/sound.rs
									
									
									
									
									
								
							
							
						
						
									
										1235
									
								
								src/sound.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										87
									
								
								src/timer.rs
									
									
									
									
									
								
							
							
						
						
									
										87
									
								
								src/timer.rs
									
									
									
									
									
								
							| @@ -1,23 +1,37 @@ | ||||
| use bitfield::bitfield; | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct Timer { | ||||
|     /// 0xFF07 | TAC - Timer Control | ||||
|     pub(crate) ctrl: TimerControl, | ||||
|     /// 0xFF05 | TIMA - Timer Counter | ||||
|     pub(crate) counter: u8, | ||||
|     counter: u8, | ||||
|     /// 0xFF06 | TMA - Timer Modulo | ||||
|     pub(crate) modulo: u8, | ||||
|     /// 0xFF04 | DIV - Divider Register | ||||
|     pub(crate) divider: u16, | ||||
|     prev_and_result: Option<u8>, | ||||
|  | ||||
|     and_result: Option<u8>, | ||||
|     interrupt: bool, | ||||
|     state: State, | ||||
| } | ||||
|  | ||||
| impl Timer { | ||||
|     pub(crate) fn clock(&mut self) { | ||||
|     pub(crate) fn tick(&mut self) { | ||||
|         use State::*; | ||||
|         use TimerSpeed::*; | ||||
|  | ||||
|         match self.state { | ||||
|             TIMAOverflow(_) | AbortedTIMAOverflow(_) => self.next(), | ||||
|             LoadTMA => { | ||||
|                 self.counter = self.modulo; | ||||
|                 self.interrupt = true; | ||||
|  | ||||
|                 self.next(); | ||||
|             } | ||||
|             Normal => {} | ||||
|         } | ||||
|  | ||||
|         self.divider = self.divider.wrapping_add(1); | ||||
|  | ||||
|         // Get Bit Position | ||||
| @@ -29,27 +43,34 @@ impl Timer { | ||||
|         }; | ||||
|  | ||||
|         let bit = (self.divider >> bit) as u8 & 0x01; | ||||
|         let timer_enable = self.ctrl.enabled() as u8; | ||||
|         let and_result = bit & timer_enable; | ||||
|         let new_result = bit & self.ctrl.enabled() as u8; | ||||
|  | ||||
|         if let Some(0x01) = self.prev_and_result { | ||||
|             if and_result == 0x00 { | ||||
|         if let Some(0x01) = self.and_result { | ||||
|             if new_result == 0x00 { | ||||
|                 // Falling Edge, increase TIMA Register | ||||
|                 self.increment_tima(); | ||||
|                 self.inc_counter(); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         self.prev_and_result = Some(and_result); | ||||
|         self.and_result = Some(new_result); | ||||
|     } | ||||
|  | ||||
|     fn increment_tima(&mut self) { | ||||
|         let (result, did_overflow) = self.counter.overflowing_add(1); | ||||
|     /// 0xFF05 | TIMA - Timer Counter | ||||
|     pub(crate) fn tima(&self) -> u8 { | ||||
|         self.counter | ||||
|     } | ||||
|  | ||||
|         self.counter = if did_overflow { | ||||
|             self.interrupt = true; | ||||
|             self.modulo | ||||
|         } else { | ||||
|             result | ||||
|     /// 0xFF05 | TIMA - Timer Counter | ||||
|     pub(crate) fn set_tima(&mut self, byte: u8) { | ||||
|         use State::*; | ||||
|  | ||||
|         match self.state { | ||||
|             Normal | AbortedTIMAOverflow(_) => self.counter = byte, | ||||
|             TIMAOverflow(step) => { | ||||
|                 self.counter = byte; | ||||
|                 self.state = AbortedTIMAOverflow(step); | ||||
|             } | ||||
|             LoadTMA => {} | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -60,6 +81,27 @@ impl Timer { | ||||
|     pub(crate) fn set_interrupt(&mut self, value: bool) { | ||||
|         self.interrupt = value; | ||||
|     } | ||||
|  | ||||
|     fn inc_counter(&mut self) { | ||||
|         let (sum, did_overflow) = self.counter.overflowing_add(1); | ||||
|         self.counter = if did_overflow { 0 } else { sum }; | ||||
|  | ||||
|         if did_overflow { | ||||
|             self.state = State::TIMAOverflow(0); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn next(&mut self) { | ||||
|         use State::*; | ||||
|  | ||||
|         self.state = match self.state { | ||||
|             Normal | LoadTMA => Normal, | ||||
|             AbortedTIMAOverflow(4) => Normal, | ||||
|             TIMAOverflow(4) => LoadTMA, | ||||
|             AbortedTIMAOverflow(step) => AbortedTIMAOverflow(step + 1), | ||||
|             TIMAOverflow(step) => TIMAOverflow(step + 1), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Default for Timer { | ||||
| @@ -70,7 +112,8 @@ impl Default for Timer { | ||||
|             modulo: 0, | ||||
|             divider: 0, | ||||
|             interrupt: false, | ||||
|             prev_and_result: None, | ||||
|             and_result: None, | ||||
|             state: State::Normal, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -132,3 +175,11 @@ impl From<TimerControl> for u8 { | ||||
|         ctrl.0 | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy, PartialEq, Eq)] | ||||
| enum State { | ||||
|     TIMAOverflow(u8), | ||||
|     AbortedTIMAOverflow(u8), | ||||
|     Normal, | ||||
|     LoadTMA, | ||||
| } | ||||
|   | ||||
| @@ -5,7 +5,7 @@ const VARIABLE_WORK_RAM_SIZE: usize = WORK_RAM_SIZE; | ||||
| const WORK_RAM_START_ADDRESS: usize = 0xC000; | ||||
| const VARIABLE_WORK_RAM_START_ADDRESS: usize = 0xD000; | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct WorkRam { | ||||
|     bank: Box<[u8; WORK_RAM_SIZE]>, | ||||
| } | ||||
| @@ -28,48 +28,25 @@ impl Default for WorkRam { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone, Copy)] | ||||
| enum BankNumber { | ||||
|     One = 1, | ||||
|     Two = 2, | ||||
|     Three = 3, | ||||
|     Four = 4, | ||||
|     Five = 5, | ||||
|     Six = 6, | ||||
|     Seven = 7, | ||||
| } | ||||
|  | ||||
| #[derive(Debug, Clone)] | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct VariableWorkRam { | ||||
|     current: BankNumber, | ||||
|     bank_n: Box<[[u8; VARIABLE_WORK_RAM_SIZE]; 7]>, // 4K for Variable amount of Banks (Banks 1 -> 7) in Game Boy Colour | ||||
|     buf: Box<[u8; VARIABLE_WORK_RAM_SIZE]>, // 4K for Variable amount of Banks (Banks 1 -> 7) in Game Boy Colour | ||||
| } | ||||
|  | ||||
| impl Default for VariableWorkRam { | ||||
|     fn default() -> Self { | ||||
|         Self { | ||||
|             current: BankNumber::One, | ||||
|             bank_n: Box::new([[0u8; VARIABLE_WORK_RAM_SIZE]; 7]), | ||||
|             buf: Box::new([0u8; VARIABLE_WORK_RAM_SIZE]), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl VariableWorkRam { | ||||
|     fn set_current_bank(&mut self, bank: BankNumber) { | ||||
|         self.current = bank; | ||||
|     } | ||||
|  | ||||
|     fn get_current_bank(&self) -> BankNumber { | ||||
|         self.current | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl BusIo for VariableWorkRam { | ||||
|     fn write_byte(&mut self, addr: u16, byte: u8) { | ||||
|         self.bank_n[self.current as usize][addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] = byte; | ||||
|         self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] = byte; | ||||
|     } | ||||
|  | ||||
|     fn read_byte(&self, addr: u16) -> u8 { | ||||
|         self.bank_n[self.current as usize][addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] | ||||
|         self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] | ||||
|     } | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user