Compare commits
34 Commits
aa22e93049
...
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 |
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",
|
||||||
|
]
|
||||||
|
}
|
569
Cargo.lock
generated
569
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -3,6 +3,7 @@ name = "gb"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Rekai Musuka <rekai@musuka.dev>"]
|
authors = ["Rekai Musuka <rekai@musuka.dev>"]
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
resolver = "2"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
@@ -11,7 +12,7 @@ anyhow = "^1.0"
|
|||||||
bitfield = "^0.13"
|
bitfield = "^0.13"
|
||||||
clap = "^2.33"
|
clap = "^2.33"
|
||||||
gilrs = "^0.8"
|
gilrs = "^0.8"
|
||||||
pixels = "^0.5"
|
pixels = "^0.6"
|
||||||
winit = "^0.25"
|
winit = "^0.25"
|
||||||
winit_input_helper = "^0.10"
|
winit_input_helper = "^0.10"
|
||||||
rodio = "^0.14"
|
rodio = "^0.14"
|
||||||
|
328
src/apu.rs
328
src/apu.rs
@@ -5,6 +5,7 @@ use types::ch1::{Sweep, SweepDirection};
|
|||||||
use types::ch3::Volume as Ch3Volume;
|
use types::ch3::Volume as Ch3Volume;
|
||||||
use types::ch4::{CounterWidth, Frequency as Ch4Frequency, PolynomialCounter};
|
use types::ch4::{CounterWidth, Frequency as Ch4Frequency, PolynomialCounter};
|
||||||
use types::common::{EnvelopeDirection, FrequencyHigh, SoundDuty, VolumeEnvelope};
|
use types::common::{EnvelopeDirection, FrequencyHigh, SoundDuty, VolumeEnvelope};
|
||||||
|
use types::fs::{FrameSequencer, State as FrameSequencerState};
|
||||||
use types::{ChannelControl, SoundOutput};
|
use types::{ChannelControl, SoundOutput};
|
||||||
|
|
||||||
pub mod gen;
|
pub mod gen;
|
||||||
@@ -25,7 +26,7 @@ pub struct Apu {
|
|||||||
/// Noise
|
/// Noise
|
||||||
ch4: Channel4,
|
ch4: Channel4,
|
||||||
|
|
||||||
// Frame Sequencer
|
sequencer: FrameSequencer,
|
||||||
div_prev: Option<u16>,
|
div_prev: Option<u16>,
|
||||||
|
|
||||||
prod: Option<SampleProducer<f32>>,
|
prod: Option<SampleProducer<f32>>,
|
||||||
@@ -42,7 +43,7 @@ impl BusIo for Apu {
|
|||||||
0x16 => self.ch2.duty(),
|
0x16 => self.ch2.duty(),
|
||||||
0x17 => self.ch2.envelope(),
|
0x17 => self.ch2.envelope(),
|
||||||
0x19 => self.ch2.freq_hi(),
|
0x19 => self.ch2.freq_hi(),
|
||||||
0x1A => self.ch3.enabled(),
|
0x1A => self.ch3.dac_enabled(),
|
||||||
0x1C => self.ch3.volume(),
|
0x1C => self.ch3.volume(),
|
||||||
0x1E => self.ch3.freq_hi(),
|
0x1E => self.ch3.freq_hi(),
|
||||||
0x21 => self.ch4.envelope(),
|
0x21 => self.ch4.envelope(),
|
||||||
@@ -70,7 +71,7 @@ impl BusIo for Apu {
|
|||||||
0x17 if self.ctrl.enabled => self.ch2.set_envelope(byte),
|
0x17 if self.ctrl.enabled => self.ch2.set_envelope(byte),
|
||||||
0x18 if self.ctrl.enabled => self.ch2.set_freq_lo(byte),
|
0x18 if self.ctrl.enabled => self.ch2.set_freq_lo(byte),
|
||||||
0x19 if self.ctrl.enabled => self.ch2.set_freq_hi(byte),
|
0x19 if self.ctrl.enabled => self.ch2.set_freq_hi(byte),
|
||||||
0x1A if self.ctrl.enabled => self.ch3.set_enabled(byte),
|
0x1A if self.ctrl.enabled => self.ch3.set_dac_enabled(byte),
|
||||||
0x1B if self.ctrl.enabled => self.ch3.set_len(byte),
|
0x1B if self.ctrl.enabled => self.ch3.set_len(byte),
|
||||||
0x1C if self.ctrl.enabled => self.ch3.set_volume(byte),
|
0x1C if self.ctrl.enabled => self.ch3.set_volume(byte),
|
||||||
0x1D if self.ctrl.enabled => self.ch3.set_freq_lo(byte),
|
0x1D if self.ctrl.enabled => self.ch3.set_freq_lo(byte),
|
||||||
@@ -96,81 +97,81 @@ impl Apu {
|
|||||||
pub(crate) fn tick(&mut self, div: u16) {
|
pub(crate) fn tick(&mut self, div: u16) {
|
||||||
self.sample_counter += SAMPLE_INCREMENT;
|
self.sample_counter += SAMPLE_INCREMENT;
|
||||||
|
|
||||||
// Length Control (256Hz)
|
// Frame Sequencer (512Hz)
|
||||||
if self.falling_edge(13, div) {
|
if self.is_falling_edge(12, div) {
|
||||||
self.handle_length();
|
use FrameSequencerState::*;
|
||||||
|
|
||||||
|
match self.sequencer.state() {
|
||||||
|
Length => self.clock_length(),
|
||||||
|
LengthAndSweep => {
|
||||||
|
self.clock_length();
|
||||||
|
self.clock_sweep();
|
||||||
|
}
|
||||||
|
Envelope => self.clock_envelope(),
|
||||||
|
Nothing => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sweep (128Hz)
|
self.sequencer.next();
|
||||||
if self.falling_edge(14, div) {
|
|
||||||
self.handle_sweep();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Volume Envelope (64Hz)
|
|
||||||
if self.falling_edge(15, div) {
|
|
||||||
self.handle_volume();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
self.div_prev = Some(div);
|
self.div_prev = Some(div);
|
||||||
|
|
||||||
self.ch1.clock();
|
self.ch1.tick();
|
||||||
self.ch2.clock();
|
self.ch2.tick();
|
||||||
self.ch3.clock();
|
self.ch3.tick();
|
||||||
self.ch4.clock();
|
self.ch4.tick();
|
||||||
|
|
||||||
if self.sample_counter >= SM83_CLOCK_SPEED {
|
if self.sample_counter >= SM83_CLOCK_SPEED {
|
||||||
self.sample_counter %= SM83_CLOCK_SPEED;
|
self.sample_counter %= SM83_CLOCK_SPEED;
|
||||||
|
|
||||||
if let Some(ref mut prod) = self.prod {
|
if let Some(ref mut prod) = self.prod {
|
||||||
if prod.available_block() {
|
if prod.available_blocking() {
|
||||||
// Sample the APU
|
// Sample the APU
|
||||||
let ch1_amplitude = self.ch1.amplitude();
|
|
||||||
let ch1_left = self.ctrl.output.ch1_left() as u8 as f32 * ch1_amplitude;
|
|
||||||
let ch1_right = self.ctrl.output.ch1_right() as u8 as f32 * ch1_amplitude;
|
|
||||||
|
|
||||||
let ch2_amplitude = self.ch2.amplitude();
|
let (left, right) = self.ctrl.out.ch1();
|
||||||
let ch2_left = self.ctrl.output.ch2_left() as u8 as f32 * ch2_amplitude;
|
let ch1_left = if left { self.ch1.amplitude() } else { 0.0 };
|
||||||
let ch2_right = self.ctrl.output.ch2_right() as u8 as f32 * ch2_amplitude;
|
let ch1_right = if right { self.ch1.amplitude() } else { 0.0 };
|
||||||
|
|
||||||
let ch3_amplitude = self.ch3.amplitude();
|
let (left, right) = self.ctrl.out.ch2();
|
||||||
let ch3_left = self.ctrl.output.ch3_left() as u8 as f32 * ch3_amplitude;
|
let ch2_left = if left { self.ch2.amplitude() } else { 0.0 };
|
||||||
let ch3_right = self.ctrl.output.ch3_right() as u8 as f32 * ch3_amplitude;
|
let ch2_right = if right { self.ch2.amplitude() } else { 0.0 };
|
||||||
|
|
||||||
let ch4_amplitude = self.ch4.amplitude();
|
let (left, right) = self.ctrl.out.ch3();
|
||||||
let ch4_left = self.ctrl.output.ch4_left() as u8 as f32 * ch4_amplitude;
|
let ch3_left = if left { self.ch3.amplitude() } else { 0.0 };
|
||||||
let ch4_right = self.ctrl.output.ch4_right() as u8 as f32 * ch4_amplitude;
|
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 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 right_mixed = (ch1_right + ch2_right + ch3_right + ch4_right) / 4.0;
|
||||||
|
|
||||||
let left = (self.ctrl.channel.left_volume() + 1.0) * left_mixed;
|
let left_sample = (self.ctrl.channel.left_volume() + 1.0) * left_mixed;
|
||||||
let right = (self.ctrl.channel.right_volume() + 1.0) * right_mixed;
|
let right_sample = (self.ctrl.channel.right_volume() + 1.0) * right_mixed;
|
||||||
|
|
||||||
prod.push(left)
|
prod.push(left_sample)
|
||||||
.and(prod.push(right))
|
.and(prod.push(right_sample))
|
||||||
.expect("Add samples to ring buffer");
|
.expect("Add samples to ring buffer");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_producer(&mut self, prod: SampleProducer<f32>) {
|
|
||||||
self.prod = Some(prod);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 0xFF26 | NR52 - Sound On/Off
|
/// 0xFF26 | NR52 - Sound On/Off
|
||||||
pub(crate) fn set_status(&mut self, byte: u8) {
|
pub(crate) fn set_status(&mut self, byte: u8) {
|
||||||
self.ctrl.enabled = (byte >> 7) & 0x01 == 0x01;
|
self.ctrl.enabled = (byte >> 7) & 0x01 == 0x01;
|
||||||
|
|
||||||
if self.ctrl.enabled {
|
if self.ctrl.enabled {
|
||||||
// Frame Sequencer reset to Step 0
|
// Frame Sequencer reset to Step 0
|
||||||
// TODO: With the current implementation of the frame sequencer, what does this even mean?
|
self.sequencer.reset();
|
||||||
|
|
||||||
// Square Duty units are reset to first step
|
// Square Duty units are reset to first step
|
||||||
self.ch1.duty_pos = 0;
|
self.ch1.duty_pos = 0;
|
||||||
self.ch2.duty_pos = 0;
|
self.ch2.duty_pos = 0;
|
||||||
|
|
||||||
// Wave Channel's sample buffer reset to 0
|
// Wave Channel's sample buffer reset to 0
|
||||||
|
self.ch3.offset = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if !self.ctrl.enabled {
|
if !self.ctrl.enabled {
|
||||||
@@ -179,6 +180,10 @@ impl Apu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn attach_producer(&mut self, prod: SampleProducer<f32>) {
|
||||||
|
self.prod = Some(prod);
|
||||||
|
}
|
||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
self.ch1.sweep = Default::default();
|
self.ch1.sweep = Default::default();
|
||||||
self.ch1.duty = Default::default();
|
self.ch1.duty = Default::default();
|
||||||
@@ -191,7 +196,7 @@ impl Apu {
|
|||||||
self.ch2.freq_lo = Default::default();
|
self.ch2.freq_lo = Default::default();
|
||||||
self.ch2.freq_hi = Default::default();
|
self.ch2.freq_hi = Default::default();
|
||||||
|
|
||||||
self.ch3.enabled = Default::default();
|
self.ch3.dac_enabled = Default::default();
|
||||||
self.ch3.len = Default::default();
|
self.ch3.len = Default::default();
|
||||||
self.ch3.volume = Default::default();
|
self.ch3.volume = Default::default();
|
||||||
self.ch3.freq_lo = Default::default();
|
self.ch3.freq_lo = Default::default();
|
||||||
@@ -203,15 +208,16 @@ impl Apu {
|
|||||||
self.ch4.freq = Default::default();
|
self.ch4.freq = Default::default();
|
||||||
|
|
||||||
self.ctrl.channel = Default::default();
|
self.ctrl.channel = Default::default();
|
||||||
self.ctrl.output = Default::default();
|
self.ctrl.out = Default::default();
|
||||||
|
|
||||||
// Disable the rest of the channels
|
// Disable the Channels
|
||||||
self.ch1.enabled = Default::default();
|
self.ch1.enabled = Default::default();
|
||||||
self.ch2.enabled = Default::default();
|
self.ch2.enabled = Default::default();
|
||||||
|
self.ch3.enabled = Default::default();
|
||||||
self.ch4.enabled = Default::default();
|
self.ch4.enabled = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clock_length(freq_hi: &FrequencyHigh, length_timer: &mut u16, enabled: &mut bool) {
|
fn process_length(freq_hi: &FrequencyHigh, length_timer: &mut u16, enabled: &mut bool) {
|
||||||
if freq_hi.length_disable() && *length_timer > 0 {
|
if freq_hi.length_disable() && *length_timer > 0 {
|
||||||
*length_timer -= 1;
|
*length_timer -= 1;
|
||||||
|
|
||||||
@@ -223,7 +229,7 @@ impl Apu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clock_length_ch4(freq: &Ch4Frequency, length_timer: &mut u16, enabled: &mut bool) {
|
fn ch4_process_length(freq: &Ch4Frequency, length_timer: &mut u16, enabled: &mut bool) {
|
||||||
if freq.length_disable() && *length_timer > 0 {
|
if freq.length_disable() && *length_timer > 0 {
|
||||||
*length_timer -= 1;
|
*length_timer -= 1;
|
||||||
|
|
||||||
@@ -235,33 +241,33 @@ impl Apu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_length(&mut self) {
|
fn clock_length(&mut self) {
|
||||||
Self::clock_length(
|
Self::process_length(
|
||||||
&self.ch1.freq_hi,
|
&self.ch1.freq_hi,
|
||||||
&mut self.ch1.length_timer,
|
&mut self.ch1.length_timer,
|
||||||
&mut self.ch1.enabled,
|
&mut self.ch1.enabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
Self::clock_length(
|
Self::process_length(
|
||||||
&self.ch2.freq_hi,
|
&self.ch2.freq_hi,
|
||||||
&mut self.ch2.length_timer,
|
&mut self.ch2.length_timer,
|
||||||
&mut self.ch2.enabled,
|
&mut self.ch2.enabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
Self::clock_length(
|
Self::process_length(
|
||||||
&self.ch3.freq_hi,
|
&self.ch3.freq_hi,
|
||||||
&mut self.ch3.length_timer,
|
&mut self.ch3.length_timer,
|
||||||
&mut self.ch3.enabled,
|
&mut self.ch3.enabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
Self::clock_length_ch4(
|
Self::ch4_process_length(
|
||||||
&self.ch4.freq,
|
&self.ch4.freq,
|
||||||
&mut self.ch4.length_timer,
|
&mut self.ch4.length_timer,
|
||||||
&mut self.ch4.enabled,
|
&mut self.ch4.enabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_sweep(&mut self) {
|
fn clock_sweep(&mut self) {
|
||||||
if self.ch1.sweep_timer != 0 {
|
if self.ch1.sweep_timer != 0 {
|
||||||
self.ch1.sweep_timer -= 1;
|
self.ch1.sweep_timer -= 1;
|
||||||
}
|
}
|
||||||
@@ -283,7 +289,7 @@ impl Apu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clock_envelope(envelope: &VolumeEnvelope, period_timer: &mut u8, current_volume: &mut u8) {
|
fn process_envelope(envelope: &VolumeEnvelope, period_timer: &mut u8, current_volume: &mut u8) {
|
||||||
use EnvelopeDirection::*;
|
use EnvelopeDirection::*;
|
||||||
|
|
||||||
if envelope.period() != 0 {
|
if envelope.period() != 0 {
|
||||||
@@ -303,29 +309,29 @@ impl Apu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_volume(&mut self) {
|
fn clock_envelope(&mut self) {
|
||||||
// Channels 1, 2 and 4 have Volume Envelopes
|
// Channels 1, 2 and 4 have Volume Envelopes
|
||||||
|
|
||||||
Self::clock_envelope(
|
Self::process_envelope(
|
||||||
&self.ch1.envelope,
|
&self.ch1.envelope,
|
||||||
&mut self.ch1.period_timer,
|
&mut self.ch1.period_timer,
|
||||||
&mut self.ch1.current_volume,
|
&mut self.ch1.current_volume,
|
||||||
);
|
);
|
||||||
|
|
||||||
Self::clock_envelope(
|
Self::process_envelope(
|
||||||
&self.ch2.envelope,
|
&self.ch2.envelope,
|
||||||
&mut self.ch2.period_timer,
|
&mut self.ch2.period_timer,
|
||||||
&mut self.ch2.current_volume,
|
&mut self.ch2.current_volume,
|
||||||
);
|
);
|
||||||
|
|
||||||
Self::clock_envelope(
|
Self::process_envelope(
|
||||||
&self.ch4.envelope,
|
&self.ch4.envelope,
|
||||||
&mut self.ch4.period_timer,
|
&mut self.ch4.period_timer,
|
||||||
&mut self.ch4.current_volume,
|
&mut self.ch4.current_volume,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn falling_edge(&self, bit: u8, div: u16) -> bool {
|
fn is_falling_edge(&self, bit: u8, div: u16) -> bool {
|
||||||
match self.div_prev {
|
match self.div_prev {
|
||||||
Some(p) => (p >> bit & 0x01) == 0x01 && (div >> bit & 0x01) == 0x00,
|
Some(p) => (p >> bit & 0x01) == 0x01 && (div >> bit & 0x01) == 0x00,
|
||||||
None => false,
|
None => false,
|
||||||
@@ -338,7 +344,7 @@ pub(crate) struct SoundControl {
|
|||||||
/// 0xFF24 | NR50 - Channel Control
|
/// 0xFF24 | NR50 - Channel Control
|
||||||
channel: ChannelControl,
|
channel: ChannelControl,
|
||||||
/// 0xFF25 | NR51 - Selection of Sound output terminal
|
/// 0xFF25 | NR51 - Selection of Sound output terminal
|
||||||
output: SoundOutput,
|
out: SoundOutput,
|
||||||
|
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
}
|
}
|
||||||
@@ -358,13 +364,13 @@ impl SoundControl {
|
|||||||
|
|
||||||
/// 0xFF25 | NR51 - Selection of Sound output terminal
|
/// 0xFF25 | NR51 - Selection of Sound output terminal
|
||||||
pub(crate) fn output(&self) -> u8 {
|
pub(crate) fn output(&self) -> u8 {
|
||||||
u8::from(self.output)
|
u8::from(self.out)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0xFF25 | NR51 - Selection of Sound output terminal
|
/// 0xFF25 | NR51 - Selection of Sound output terminal
|
||||||
pub(crate) fn set_output(&mut self, byte: u8) {
|
pub(crate) fn set_output(&mut self, byte: u8) {
|
||||||
if self.enabled {
|
if self.enabled {
|
||||||
self.output = byte.into();
|
self.out = byte.into();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -411,23 +417,6 @@ pub(crate) struct Channel1 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Channel1 {
|
impl Channel1 {
|
||||||
fn amplitude(&self) -> f32 {
|
|
||||||
let dac_input = self.duty.wave_pattern().amplitude(self.duty_pos) * self.current_volume;
|
|
||||||
|
|
||||||
(dac_input as f32 / 7.5) - 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clock(&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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 0xFF10 | NR10 - Channel 1 Sweep Register
|
/// 0xFF10 | NR10 - Channel 1 Sweep Register
|
||||||
pub(crate) fn sweep(&self) -> u8 {
|
pub(crate) fn sweep(&self) -> u8 {
|
||||||
u8::from(self.sweep) | 0x80
|
u8::from(self.sweep) | 0x80
|
||||||
@@ -456,7 +445,11 @@ impl Channel1 {
|
|||||||
|
|
||||||
/// 0xFF12 | NR12 - Channel 1 Volume Envelope
|
/// 0xFF12 | NR12 - Channel 1 Volume Envelope
|
||||||
pub(crate) fn set_envelope(&mut self, byte: u8) {
|
pub(crate) fn set_envelope(&mut self, byte: u8) {
|
||||||
self.envelope = byte.into()
|
self.envelope = byte.into();
|
||||||
|
|
||||||
|
if !self.is_dac_enabled() {
|
||||||
|
self.enabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0xFF13 | NR13 - Channel 1 Frequency low (lower 8 bits only)
|
/// 0xFF13 | NR13 - Channel 1 Frequency low (lower 8 bits only)
|
||||||
@@ -475,22 +468,8 @@ impl Channel1 {
|
|||||||
|
|
||||||
// If this bit is set, a trigger event occurs
|
// If this bit is set, a trigger event occurs
|
||||||
if self.freq_hi.initial() {
|
if self.freq_hi.initial() {
|
||||||
// Envelope Behaviour during trigger event
|
if self.is_dac_enabled() {
|
||||||
self.period_timer = self.envelope.period();
|
self.enabled = true;
|
||||||
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 };
|
|
||||||
|
|
||||||
if sweep_period != 0 || sweep_shift != 0 {
|
|
||||||
self.sweep_enabled = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if sweep_shift != 0 {
|
|
||||||
let _ = self.calc_sweep_freq();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Length behaviour during trigger event
|
// Length behaviour during trigger event
|
||||||
@@ -498,7 +477,44 @@ impl Channel1 {
|
|||||||
self.length_timer = 64;
|
self.length_timer = 64;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.enabled = true;
|
// 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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -529,6 +545,10 @@ impl Channel1 {
|
|||||||
fn frequency(&self) -> u16 {
|
fn frequency(&self) -> u16 {
|
||||||
(self.freq_hi.freq_bits() as u16) << 8 | self.freq_lo as 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)]
|
#[derive(Debug, Default)]
|
||||||
@@ -556,23 +576,6 @@ pub(crate) struct Channel2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Channel2 {
|
impl Channel2 {
|
||||||
fn amplitude(&self) -> f32 {
|
|
||||||
let dac_input = self.duty.wave_pattern().amplitude(self.duty_pos) * self.current_volume;
|
|
||||||
|
|
||||||
(dac_input as f32 / 7.5) - 1.0
|
|
||||||
}
|
|
||||||
|
|
||||||
fn clock(&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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty
|
/// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty
|
||||||
pub(crate) fn duty(&self) -> u8 {
|
pub(crate) fn duty(&self) -> u8 {
|
||||||
u8::from(self.duty) | 0x3F
|
u8::from(self.duty) | 0x3F
|
||||||
@@ -591,7 +594,11 @@ impl Channel2 {
|
|||||||
|
|
||||||
/// 0xFF17 | NR22 - Channel 2 Volume ENvelope
|
/// 0xFF17 | NR22 - Channel 2 Volume ENvelope
|
||||||
pub(crate) fn set_envelope(&mut self, byte: u8) {
|
pub(crate) fn set_envelope(&mut self, byte: u8) {
|
||||||
self.envelope = byte.into()
|
self.envelope = byte.into();
|
||||||
|
|
||||||
|
if !self.is_dac_enabled() {
|
||||||
|
self.enabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0xFF18 | NR23 - Channel 2 Frequency low (lower 8 bits only)
|
/// 0xFF18 | NR23 - Channel 2 Frequency low (lower 8 bits only)
|
||||||
@@ -618,19 +625,47 @@ impl Channel2 {
|
|||||||
self.length_timer = 64;
|
self.length_timer = 64;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.is_dac_enabled() {
|
||||||
self.enabled = true;
|
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 {
|
fn frequency(&self) -> u16 {
|
||||||
(self.freq_hi.freq_bits() as u16) << 8 | self.freq_lo as 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)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct Channel3 {
|
pub(crate) struct Channel3 {
|
||||||
/// 0xFF1A | NR30 - Channel 3 Sound on/off
|
/// 0xFF1A | NR30 - Channel 3 Sound on/off
|
||||||
enabled: bool,
|
dac_enabled: bool,
|
||||||
/// 0xFF1B | NR31 - Sound Length
|
/// 0xFF1B | NR31 - Sound Length
|
||||||
len: u8,
|
len: u8,
|
||||||
/// 0xFF1C | NR32 - Channel 3 Volume
|
/// 0xFF1C | NR32 - Channel 3 Volume
|
||||||
@@ -647,6 +682,8 @@ pub(crate) struct Channel3 {
|
|||||||
|
|
||||||
freq_timer: u16,
|
freq_timer: u16,
|
||||||
offset: u8,
|
offset: u8,
|
||||||
|
|
||||||
|
enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BusIo for Channel3 {
|
impl BusIo for Channel3 {
|
||||||
@@ -671,13 +708,17 @@ impl Channel3 {
|
|||||||
const WAVE_RAM_START_ADDR: u16 = 0xFF30;
|
const WAVE_RAM_START_ADDR: u16 = 0xFF30;
|
||||||
|
|
||||||
/// 0xFF1A | NR30 - Channel 3 Sound on/off
|
/// 0xFF1A | NR30 - Channel 3 Sound on/off
|
||||||
pub(crate) fn enabled(&self) -> u8 {
|
pub(crate) fn dac_enabled(&self) -> u8 {
|
||||||
((self.enabled as u8) << 7) | 0x7F
|
((self.dac_enabled as u8) << 7) | 0x7F
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0xFF1A | NR30 - Channel 3 Sound on/off
|
/// 0xFF1A | NR30 - Channel 3 Sound on/off
|
||||||
pub(crate) fn set_enabled(&mut self, byte: u8) {
|
pub(crate) fn set_dac_enabled(&mut self, byte: u8) {
|
||||||
self.enabled = (byte >> 7) & 0x01 == 0x01;
|
self.dac_enabled = (byte >> 7) & 0x01 == 0x01;
|
||||||
|
|
||||||
|
if !self.dac_enabled {
|
||||||
|
self.enabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0xFF1B | NR31 - Sound Length
|
/// 0xFF1B | NR31 - Sound Length
|
||||||
@@ -724,18 +765,13 @@ impl Channel3 {
|
|||||||
self.length_timer = 256;
|
self.length_timer = 256;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if self.dac_enabled {
|
||||||
self.enabled = true;
|
self.enabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn amplitude(&self) -> f32 {
|
|
||||||
let dac_input =
|
|
||||||
(self.read_sample(self.offset) >> self.volume.shift_count()) * self.enabled as u8;
|
|
||||||
|
|
||||||
(dac_input as f32 / 7.5) - 1.0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clock(&mut self) {
|
fn tick(&mut self) {
|
||||||
if self.freq_timer != 0 {
|
if self.freq_timer != 0 {
|
||||||
self.freq_timer -= 1;
|
self.freq_timer -= 1;
|
||||||
}
|
}
|
||||||
@@ -746,6 +782,17 @@ impl Channel3 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
fn read_sample(&self, index: u8) -> u8 {
|
||||||
let i = (index / 2) as usize;
|
let i = (index / 2) as usize;
|
||||||
|
|
||||||
@@ -793,7 +840,7 @@ impl Channel4 {
|
|||||||
/// 0xFF20 | NR41 - Channel 4 Sound Length
|
/// 0xFF20 | NR41 - Channel 4 Sound Length
|
||||||
pub(crate) fn set_len(&mut self, byte: u8) {
|
pub(crate) fn set_len(&mut self, byte: u8) {
|
||||||
self.len = byte & 0x3F;
|
self.len = byte & 0x3F;
|
||||||
self.length_timer = 256 - self.len as u16;
|
self.length_timer = 64 - self.len as u16;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0xFF21 | NR42 - Channel 4 Volume Envelope
|
/// 0xFF21 | NR42 - Channel 4 Volume Envelope
|
||||||
@@ -803,7 +850,11 @@ impl Channel4 {
|
|||||||
|
|
||||||
/// 0xFF21 | NR42 - Channel 4 Volume Envelope
|
/// 0xFF21 | NR42 - Channel 4 Volume Envelope
|
||||||
pub(crate) fn set_envelope(&mut self, byte: u8) {
|
pub(crate) fn set_envelope(&mut self, byte: u8) {
|
||||||
self.envelope = byte.into()
|
self.envelope = byte.into();
|
||||||
|
|
||||||
|
if !self.is_dac_enabled() {
|
||||||
|
self.enabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0xFF22 | NR43 - Chanel 4 Polynomial Counter
|
/// 0xFF22 | NR43 - Chanel 4 Polynomial Counter
|
||||||
@@ -838,17 +889,13 @@ impl Channel4 {
|
|||||||
// LFSR behaviour during trigger event
|
// LFSR behaviour during trigger event
|
||||||
self.lf_shift = 0x7FFF;
|
self.lf_shift = 0x7FFF;
|
||||||
|
|
||||||
|
if self.is_dac_enabled() {
|
||||||
self.enabled = true;
|
self.enabled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn amplitude(&self) -> f32 {
|
|
||||||
let dac_input = (!self.lf_shift & 0x01) as u8 * self.current_volume;
|
|
||||||
|
|
||||||
(dac_input as f32 / 7.5) - 1.0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clock(&mut self) {
|
fn tick(&mut self) {
|
||||||
if self.freq_timer != 0 {
|
if self.freq_timer != 0 {
|
||||||
self.freq_timer -= 1;
|
self.freq_timer -= 1;
|
||||||
}
|
}
|
||||||
@@ -866,6 +913,21 @@ impl Channel4 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
fn divisor(code: u8) -> u8 {
|
||||||
if code == 0 {
|
if code == 0 {
|
||||||
return 8;
|
return 8;
|
||||||
|
@@ -48,7 +48,7 @@ impl<T> SampleProducer<T> {
|
|||||||
self.inner.slots() > 2
|
self.inner.slots() > 2
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn available_block(&self) -> bool {
|
pub(crate) fn available_blocking(&self) -> bool {
|
||||||
loop {
|
loop {
|
||||||
if self.inner.slots() > 2 {
|
if self.inner.slots() > 2 {
|
||||||
break true;
|
break true;
|
||||||
|
@@ -73,7 +73,7 @@ pub(crate) mod ch1 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) mod ch3 {
|
pub(super) mod ch3 {
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub(crate) enum Volume {
|
pub(crate) enum Volume {
|
||||||
Mute = 0,
|
Mute = 0,
|
||||||
@@ -102,7 +102,7 @@ pub(crate) mod ch3 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) mod ch4 {
|
pub(super) mod ch4 {
|
||||||
use super::bitfield;
|
use super::bitfield;
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
@@ -217,7 +217,7 @@ pub(crate) mod ch4 {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) mod common {
|
pub(super) mod common {
|
||||||
use super::bitfield;
|
use super::bitfield;
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
@@ -440,6 +440,24 @@ bitfield! {
|
|||||||
pub ch1_right, _: 0;
|
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 Copy for SoundOutput {}
|
||||||
impl Clone for SoundOutput {
|
impl Clone for SoundOutput {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
@@ -508,3 +526,52 @@ impl From<ChannelControl> for u8 {
|
|||||||
ctrl.0
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
43
src/bus.rs
43
src/bus.rs
@@ -7,14 +7,13 @@ use crate::ppu::{Ppu, PpuMode};
|
|||||||
use crate::serial::Serial;
|
use crate::serial::Serial;
|
||||||
use crate::timer::Timer;
|
use crate::timer::Timer;
|
||||||
use crate::work_ram::{VariableWorkRam, WorkRam};
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct Bus {
|
pub struct Bus {
|
||||||
boot: Option<[u8; BOOT_ROM_SIZE]>, // Boot ROM is 256b long
|
boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long
|
||||||
cartridge: Option<Cartridge>,
|
cart: Option<Cartridge>,
|
||||||
pub(crate) ppu: Ppu,
|
pub(crate) ppu: Ppu,
|
||||||
work_ram: WorkRam,
|
work_ram: WorkRam,
|
||||||
var_ram: VariableWorkRam,
|
var_ram: VariableWorkRam,
|
||||||
@@ -30,7 +29,7 @@ impl Default for Bus {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
boot: None,
|
boot: None,
|
||||||
cartridge: None,
|
cart: None,
|
||||||
ppu: Default::default(),
|
ppu: Default::default(),
|
||||||
work_ram: Default::default(),
|
work_ram: Default::default(),
|
||||||
var_ram: Default::default(),
|
var_ram: Default::default(),
|
||||||
@@ -45,25 +44,19 @@ impl Default for Bus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Bus {
|
impl Bus {
|
||||||
pub(crate) fn with_boot(path: &str) -> anyhow::Result<Self> {
|
pub(crate) fn with_boot(rom: [u8; 256]) -> Self {
|
||||||
let mut file = File::open(path)?;
|
Self {
|
||||||
let mut boot_rom = [0u8; 256];
|
boot: Some(rom),
|
||||||
|
|
||||||
file.read_exact(&mut boot_rom)?;
|
|
||||||
|
|
||||||
Ok(Self {
|
|
||||||
boot: Some(boot_rom),
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> {
|
pub(crate) fn load_cart(&mut self, rom: Vec<u8>) {
|
||||||
self.cartridge = Some(Cartridge::new(path)?);
|
self.cart = Some(Cartridge::new(rom));
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn rom_title(&self) -> Option<&str> {
|
pub(crate) fn cart_title(&self) -> Option<&str> {
|
||||||
self.cartridge.as_ref()?.title()
|
self.cart.as_ref()?.title()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@@ -104,13 +97,13 @@ impl Bus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.cartridge.as_ref() {
|
match self.cart.as_ref() {
|
||||||
Some(cart) => cart.read_byte(addr),
|
Some(cart) => cart.read_byte(addr),
|
||||||
None => panic!("Tried to read from a non-existent cartridge"),
|
None => panic!("Tried to read from a non-existent cartridge"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0x8000..=0x9FFF => self.ppu.read_byte(addr), // 8KB Video RAM
|
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
|
// 8KB External RAM
|
||||||
Some(cart) => cart.read_byte(addr),
|
Some(cart) => cart.read_byte(addr),
|
||||||
None => panic!("Tried to read from a non-existent cartridge"),
|
None => panic!("Tried to read from a non-existent cartridge"),
|
||||||
@@ -157,7 +150,7 @@ impl BusIo for Bus {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match self.cartridge.as_ref() {
|
match self.cart.as_ref() {
|
||||||
Some(cart) => cart.read_byte(addr),
|
Some(cart) => cart.read_byte(addr),
|
||||||
None => panic!("Tried to read from a non-existent cartridge"),
|
None => panic!("Tried to read from a non-existent cartridge"),
|
||||||
}
|
}
|
||||||
@@ -169,7 +162,7 @@ impl BusIo for Bus {
|
|||||||
_ => self.ppu.read_byte(addr),
|
_ => self.ppu.read_byte(addr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0xA000..=0xBFFF => match self.cartridge.as_ref() {
|
0xA000..=0xBFFF => match self.cart.as_ref() {
|
||||||
// 8KB External RAM
|
// 8KB External RAM
|
||||||
Some(cart) => cart.read_byte(addr),
|
Some(cart) => cart.read_byte(addr),
|
||||||
None => panic!("Tried to read from a non-existent cartridge"),
|
None => panic!("Tried to read from a non-existent cartridge"),
|
||||||
@@ -262,7 +255,7 @@ impl BusIo for Bus {
|
|||||||
0x0000..=0x7FFF => {
|
0x0000..=0x7FFF => {
|
||||||
// 16KB ROM bank 00 (ends at 0x3FFF)
|
// 16KB ROM bank 00 (ends at 0x3FFF)
|
||||||
// and 16KB ROM Bank 01 -> NN (switchable via MB)
|
// 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),
|
Some(cart) => cart.write_byte(addr, byte),
|
||||||
None => panic!("Tried to write into non-existent cartridge"),
|
None => panic!("Tried to write into non-existent cartridge"),
|
||||||
}
|
}
|
||||||
@@ -276,7 +269,7 @@ impl BusIo for Bus {
|
|||||||
}
|
}
|
||||||
0xA000..=0xBFFF => {
|
0xA000..=0xBFFF => {
|
||||||
// 8KB External RAM
|
// 8KB External RAM
|
||||||
match self.cartridge.as_mut() {
|
match self.cart.as_mut() {
|
||||||
Some(cart) => cart.write_byte(addr, byte),
|
Some(cart) => cart.write_byte(addr, byte),
|
||||||
None => panic!("Tried to write into non-existent cartridge"),
|
None => panic!("Tried to write into non-existent cartridge"),
|
||||||
}
|
}
|
||||||
|
461
src/cartridge.rs
461
src/cartridge.rs
@@ -1,7 +1,3 @@
|
|||||||
use std::fs::File;
|
|
||||||
use std::io::{self, Read};
|
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use crate::bus::BusIo;
|
use crate::bus::BusIo;
|
||||||
|
|
||||||
const RAM_SIZE_ADDRESS: usize = 0x0149;
|
const RAM_SIZE_ADDRESS: usize = 0x0149;
|
||||||
@@ -17,81 +13,45 @@ pub(crate) struct Cartridge {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Cartridge {
|
impl Cartridge {
|
||||||
pub(crate) fn new<P: AsRef<Path> + ?Sized>(path: &P) -> io::Result<Self> {
|
pub(crate) fn new(memory: Vec<u8>) -> Self {
|
||||||
let mut memory = vec![];
|
|
||||||
let mut rom = File::open(path)?;
|
|
||||||
rom.read_to_end(&mut memory)?;
|
|
||||||
|
|
||||||
let title = Self::find_title(&memory);
|
let title = Self::find_title(&memory);
|
||||||
eprintln!("Cartridge Title: {:?}", title);
|
eprintln!("Cartridge Title: {:?}", title);
|
||||||
|
|
||||||
Ok(Self {
|
Self {
|
||||||
mbc: Self::detect_mbc(&memory),
|
mbc: Self::detect_mbc(&memory),
|
||||||
title,
|
title,
|
||||||
memory,
|
memory,
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn detect_mbc(memory: &[u8]) -> Box<dyn MBCIo> {
|
fn detect_mbc(memory: &[u8]) -> Box<dyn MBCIo> {
|
||||||
let ram_size = Self::find_ram_size(memory);
|
let ram_size = Self::detect_ram_info(memory);
|
||||||
let bank_count = Self::find_bank_count(memory);
|
let rom_size = Self::detect_rom_info(memory);
|
||||||
let mbc_kind = Self::find_mbc(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 Ram Size: {} bytes", ram_cap);
|
||||||
eprintln!("Cartridge ROM Size: {} bytes", bank_count.size());
|
eprintln!("Cartridge ROM Size: {} bytes", rom_size.capacity());
|
||||||
eprintln!("MBC Type: {:?}", mbc_kind);
|
eprintln!("MBC Type: {:?}", mbc_kind);
|
||||||
|
|
||||||
match mbc_kind {
|
match mbc_kind {
|
||||||
MBCKind::None => Box::new(NoMBC {}),
|
MBCKind::None => Box::new(NoMBC),
|
||||||
MBCKind::MBC1 => {
|
MBCKind::MBC1 => Box::new(MBC1::new(ram_size, rom_size)),
|
||||||
let mbc = MBC1 {
|
MBCKind::MBC1WithBattery => Box::new(MBC1::new(ram_size, rom_size)), // TODO: Implement Saving
|
||||||
ram_size,
|
MBCKind::MBC2 => Box::new(MBC2::new(rom_cap)),
|
||||||
ram: vec![0; ram_byte_count as usize],
|
MBCKind::MBC2WithBattery => Box::new(MBC2::new(rom_cap)), // TODO: Implement Saving
|
||||||
bank_count,
|
MBCKind::MBC3 => Box::new(MBC3::new(ram_cap)),
|
||||||
..Default::default()
|
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
|
||||||
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"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_title(memory: &[u8]) -> Option<String> {
|
fn find_title(memory: &[u8]) -> Option<String> {
|
||||||
let slice = &memory[ROM_TITLE_RANGE];
|
let slice = &memory[ROM_TITLE_RANGE];
|
||||||
let with_nulls = std::str::from_utf8(slice).ok();
|
let with_nulls = std::str::from_utf8(slice).ok();
|
||||||
let trimmed = with_nulls.map(|s| s.trim_matches('\0'));
|
let trimmed = with_nulls.map(|s| s.trim_matches('\0').trim());
|
||||||
|
|
||||||
match trimmed {
|
match trimmed {
|
||||||
Some("") | None => None,
|
Some("") | None => None,
|
||||||
@@ -103,25 +63,29 @@ impl Cartridge {
|
|||||||
self.title.as_deref()
|
self.title.as_deref()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_ram_size(memory: &[u8]) -> RamSize {
|
fn detect_ram_info(memory: &[u8]) -> RamSize {
|
||||||
let id = memory[RAM_SIZE_ADDRESS];
|
let id = memory[RAM_SIZE_ADDRESS];
|
||||||
id.into()
|
id.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_bank_count(memory: &[u8]) -> BankCount {
|
fn detect_rom_info(memory: &[u8]) -> RomSize {
|
||||||
let id = memory[ROM_SIZE_ADDRESS];
|
let id = memory[ROM_SIZE_ADDRESS];
|
||||||
id.into()
|
id.into()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn find_mbc(memory: &[u8]) -> MBCKind {
|
fn find_mbc(memory: &[u8]) -> MBCKind {
|
||||||
|
use MBCKind::*;
|
||||||
|
|
||||||
match memory[MBC_TYPE_ADDRESS] {
|
match memory[MBC_TYPE_ADDRESS] {
|
||||||
0x00 => MBCKind::None,
|
0x00 => None,
|
||||||
0x01 => MBCKind::MBC1,
|
0x01 | 0x02 => MBC1,
|
||||||
0x02 => MBCKind::MBC1,
|
0x03 => MBC1WithBattery,
|
||||||
0x03 => MBCKind::MBC1WithBattery,
|
0x05 => MBC2,
|
||||||
0x19 => MBCKind::MBC5,
|
0x06 => MBC2WithBattery,
|
||||||
0x13 => MBCKind::MBC3WithBattery,
|
0x19 | 0x1A => MBC5,
|
||||||
0x11 => MBCKind::MBC3,
|
0x1B => MBC5WithBattery,
|
||||||
|
0x13 => MBC3WithBattery,
|
||||||
|
0x11 | 0x12 => MBC3,
|
||||||
id => unimplemented!("id {:#04X} is an unsupported MBC", id),
|
id => unimplemented!("id {:#04X} is an unsupported MBC", id),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -150,84 +114,82 @@ struct MBC1 {
|
|||||||
ram_bank: u8,
|
ram_bank: u8,
|
||||||
mode: bool,
|
mode: bool,
|
||||||
ram_size: RamSize,
|
ram_size: RamSize,
|
||||||
ram: Vec<u8>,
|
memory: Vec<u8>,
|
||||||
bank_count: BankCount,
|
rom_size: RomSize,
|
||||||
ram_enabled: bool,
|
mem_enabled: bool,
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for MBC1 {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
rom_bank: 0x01,
|
|
||||||
ram_bank: Default::default(),
|
|
||||||
mode: Default::default(),
|
|
||||||
ram_size: Default::default(),
|
|
||||||
ram: Default::default(),
|
|
||||||
bank_count: Default::default(),
|
|
||||||
ram_enabled: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MBC1 {
|
impl MBC1 {
|
||||||
fn zero_bank(&self) -> u8 {
|
fn new(ram_size: RamSize, rom_size: RomSize) -> Self {
|
||||||
use BankCount::*;
|
Self {
|
||||||
|
rom_bank: 0x01,
|
||||||
|
memory: vec![0; ram_size.capacity() as usize],
|
||||||
|
ram_size,
|
||||||
|
rom_size,
|
||||||
|
ram_bank: Default::default(),
|
||||||
|
mode: Default::default(),
|
||||||
|
mem_enabled: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
match self.bank_count {
|
fn zero_bank(&self) -> u8 {
|
||||||
|
use RomSize::*;
|
||||||
|
|
||||||
|
match self.rom_size {
|
||||||
None | Four | Eight | Sixteen | ThirtyTwo => 0x00,
|
None | Four | Eight | Sixteen | ThirtyTwo => 0x00,
|
||||||
SixtyFour => (self.ram_bank & 0x01) << 5,
|
SixtyFour => (self.ram_bank & 0x01) << 5,
|
||||||
OneHundredTwentyEight => (self.ram_bank & 0x03) << 5,
|
OneTwentyEight => (self.ram_bank & 0x03) << 5,
|
||||||
_ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count),
|
_ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _mbcm_zero_bank(&self) -> u8 {
|
fn _mbcm_zero_bank(&self) -> u8 {
|
||||||
use BankCount::*;
|
use RomSize::*;
|
||||||
|
|
||||||
match self.bank_count {
|
match self.rom_size {
|
||||||
None | Four | Eight | Sixteen | ThirtyTwo => 0x00,
|
None | Four | Eight | Sixteen | ThirtyTwo => 0x00,
|
||||||
SixtyFour => (self.ram_bank & 0x03) << 4,
|
SixtyFour => (self.ram_bank & 0x03) << 4,
|
||||||
OneHundredTwentyEight => (self.ram_bank & 0x03) << 5,
|
OneTwentyEight => (self.ram_bank & 0x03) << 5,
|
||||||
_ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count),
|
_ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn high_bank(&self) -> u8 {
|
fn high_bank(&self) -> u8 {
|
||||||
use BankCount::*;
|
use RomSize::*;
|
||||||
|
|
||||||
let base = self.rom_bank & self.rom_size_mask();
|
let base = self.rom_bank & self.rom_size_mask();
|
||||||
|
|
||||||
match self.bank_count {
|
match self.rom_size {
|
||||||
None | Four | Eight | Sixteen | ThirtyTwo => base,
|
None | Four | Eight | Sixteen | ThirtyTwo => base,
|
||||||
SixtyFour => base & !(0x01 << 5) | ((self.ram_bank & 0x01) << 5),
|
SixtyFour => base & !(0x01 << 5) | ((self.ram_bank & 0x01) << 5),
|
||||||
OneHundredTwentyEight => base & !(0x03 << 5) | ((self.ram_bank & 0x03) << 5),
|
OneTwentyEight => base & !(0x03 << 5) | ((self.ram_bank & 0x03) << 5),
|
||||||
_ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count),
|
_ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rom_size_mask(&self) -> u8 {
|
fn rom_size_mask(&self) -> u8 {
|
||||||
use BankCount::*;
|
use RomSize::*;
|
||||||
|
|
||||||
match self.bank_count {
|
match self.rom_size {
|
||||||
None => 0b00000001,
|
None => 0b00000001,
|
||||||
Four => 0b00000011,
|
Four => 0b00000011,
|
||||||
Eight => 0b00000111,
|
Eight => 0b00000111,
|
||||||
Sixteen => 0b00001111,
|
Sixteen => 0b00001111,
|
||||||
ThirtyTwo | SixtyFour | OneHundredTwentyEight => 0b00011111,
|
ThirtyTwo | SixtyFour | OneTwentyEight => 0b00011111,
|
||||||
_ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count),
|
_ => 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::*;
|
use RamSize::*;
|
||||||
|
|
||||||
match self.ram_size {
|
match self.ram_size {
|
||||||
_2KB | _8KB => (addr - 0xA000) % self.ram_size.len() as u16,
|
Unused | One => (addr as usize - 0xA000) % self.ram_size.capacity(),
|
||||||
_32KB => {
|
Four => {
|
||||||
if self.mode {
|
if self.mode {
|
||||||
0x2000 * self.ram_bank as u16 + (addr - 0xA000)
|
0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)
|
||||||
} else {
|
} else {
|
||||||
addr - 0xA000
|
addr as usize - 0xA000
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => unreachable!("RAM size can not be greater than 32KB on MBC1"),
|
_ => unreachable!("RAM size can not be greater than 32KB on MBC1"),
|
||||||
@@ -240,44 +202,34 @@ impl MBCIo for MBC1 {
|
|||||||
use MBCResult::*;
|
use MBCResult::*;
|
||||||
|
|
||||||
match addr {
|
match addr {
|
||||||
0x0000..=0x3FFF => {
|
0x0000..=0x3FFF if self.mode => {
|
||||||
if self.mode {
|
|
||||||
Address(0x4000 * self.zero_bank() as usize + addr as usize)
|
Address(0x4000 * self.zero_bank() as usize + addr as usize)
|
||||||
} else {
|
|
||||||
Address(addr as usize)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
0x0000..=0x3FFF => Address(addr as usize),
|
||||||
0x4000..=0x7FFF => {
|
0x4000..=0x7FFF => {
|
||||||
Address(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000))
|
Address(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000))
|
||||||
}
|
}
|
||||||
0xA000..=0xBFFF => {
|
0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => {
|
||||||
if self.ram_enabled {
|
Value(self.memory[self.ram_addr(addr)])
|
||||||
Value(self.ram[self.ram_addr(addr) as usize])
|
|
||||||
} else {
|
|
||||||
Value(0xFF)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
0xA000..=0xBFFF => Value(0xFF),
|
||||||
_ => unreachable!("A read from {:#06X} should not be handled by MBC1", addr),
|
_ => unreachable!("A read from {:#06X} should not be handled by MBC1", addr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_write(&mut self, addr: u16, byte: u8) {
|
fn handle_write(&mut self, addr: u16, byte: u8) {
|
||||||
match addr {
|
match addr {
|
||||||
0x0000..=0x1FFF => self.ram_enabled = (byte & 0x0F) == 0x0A,
|
0x0000..=0x1FFF => self.mem_enabled = (byte & 0x0F) == 0x0A,
|
||||||
0x2000..=0x3FFF => {
|
0x2000..=0x3FFF => {
|
||||||
self.rom_bank = if byte == 0x00 {
|
let value = byte & 0x1F;
|
||||||
0x01
|
let masked_value = byte & self.rom_size_mask();
|
||||||
} else {
|
self.rom_bank = if value == 0 { 0x01 } else { masked_value };
|
||||||
byte & self.rom_size_mask()
|
|
||||||
};
|
|
||||||
|
|
||||||
self.rom_bank &= 0x1F;
|
|
||||||
}
|
}
|
||||||
0x4000..=0x5FFF => self.ram_bank = byte & 0x03,
|
0x4000..=0x5FFF => self.ram_bank = byte & 0x03,
|
||||||
0x6000..=0x7FFF => self.mode = (byte & 0x01) == 0x01,
|
0x6000..=0x7FFF => self.mode = (byte & 0x01) == 0x01,
|
||||||
0xA000..=0xBFFF if self.ram_enabled => {
|
0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => {
|
||||||
let ram_addr = self.ram_addr(addr) as usize;
|
let ram_addr = self.ram_addr(addr);
|
||||||
self.ram[ram_addr] = byte;
|
self.memory[ram_addr] = byte;
|
||||||
}
|
}
|
||||||
0xA000..=0xBFFF => {} // Ram isn't enabled, ignored write
|
0xA000..=0xBFFF => {} // Ram isn't enabled, ignored write
|
||||||
_ => unreachable!("A write to {:#06X} should not be handled by MBC1", addr),
|
_ => unreachable!("A write to {:#06X} should not be handled by MBC1", addr),
|
||||||
@@ -291,22 +243,34 @@ enum MBC3Device {
|
|||||||
RealTimeClock,
|
RealTimeClock,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug)]
|
||||||
struct MBC3 {
|
struct MBC3 {
|
||||||
/// 7-bit Number
|
/// 7-bit Number
|
||||||
rom_bank: u8,
|
rom_bank: u8,
|
||||||
/// 2-bit Number
|
/// 2-bit Number
|
||||||
ram_bank: u8,
|
ram_bank: u8,
|
||||||
|
|
||||||
devices_enabled: bool,
|
devs_enabled: bool,
|
||||||
currently_mapped: Option<MBC3Device>,
|
mapped: Option<MBC3Device>,
|
||||||
ram_size: RamSize,
|
memory: Vec<u8>,
|
||||||
ram: Vec<u8>,
|
|
||||||
|
|
||||||
// RTC Data Latch Previous Write
|
// RTC Data Latch Previous Write
|
||||||
prev_latch_write: Option<u8>,
|
prev_latch_write: Option<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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 {
|
impl MBCIo for MBC3 {
|
||||||
fn handle_read(&self, addr: u16) -> MBCResult {
|
fn handle_read(&self, addr: u16) -> MBCResult {
|
||||||
use MBCResult::*;
|
use MBCResult::*;
|
||||||
@@ -314,11 +278,11 @@ impl MBCIo for MBC3 {
|
|||||||
let res = match addr {
|
let res = match addr {
|
||||||
0x0000..=0x3FFF => Address(addr as usize),
|
0x0000..=0x3FFF => Address(addr as usize),
|
||||||
0x4000..=0x7FFF => Address(0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)),
|
0x4000..=0x7FFF => Address(0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)),
|
||||||
0xA000..=0xBFFF => match self.currently_mapped {
|
0xA000..=0xBFFF => match self.mapped {
|
||||||
Some(MBC3Device::ExternalRam) if self.devices_enabled => {
|
Some(MBC3Device::ExternalRam) if self.devs_enabled => {
|
||||||
Value(self.ram[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
|
Value(self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
|
||||||
}
|
}
|
||||||
Some(MBC3Device::RealTimeClock) if self.devices_enabled => {
|
Some(MBC3Device::RealTimeClock) if self.devs_enabled => {
|
||||||
todo!("Return Latched value of register")
|
todo!("Return Latched value of register")
|
||||||
}
|
}
|
||||||
_ => Value(0xFF),
|
_ => Value(0xFF),
|
||||||
@@ -331,7 +295,7 @@ impl MBCIo for MBC3 {
|
|||||||
|
|
||||||
fn handle_write(&mut self, addr: u16, byte: u8) {
|
fn handle_write(&mut self, addr: u16, byte: u8) {
|
||||||
match addr {
|
match addr {
|
||||||
0x000..=0x1FFF => self.devices_enabled = (byte & 0x0F) == 0x0A, // Enable External RAM and Access to RTC if there is one
|
0x000..=0x1FFF => self.devs_enabled = (byte & 0x0F) == 0x0A, // Enable External RAM and Access to RTC if there is one
|
||||||
0x2000..=0x3FFF => {
|
0x2000..=0x3FFF => {
|
||||||
self.rom_bank = match byte {
|
self.rom_bank = match byte {
|
||||||
0x00 => 0x01,
|
0x00 => 0x01,
|
||||||
@@ -341,10 +305,10 @@ impl MBCIo for MBC3 {
|
|||||||
0x4000..=0x5FFF => match byte {
|
0x4000..=0x5FFF => match byte {
|
||||||
0x00 | 0x01 | 0x02 | 0x03 => {
|
0x00 | 0x01 | 0x02 | 0x03 => {
|
||||||
self.ram_bank = byte & 0x03;
|
self.ram_bank = byte & 0x03;
|
||||||
self.currently_mapped = Some(MBC3Device::ExternalRam);
|
self.mapped = Some(MBC3Device::ExternalRam);
|
||||||
}
|
}
|
||||||
0x08 | 0x09 | 0x0A | 0x0B | 0x0C => {
|
0x08 | 0x09 | 0x0A | 0x0B | 0x0C => {
|
||||||
self.currently_mapped = Some(MBC3Device::RealTimeClock);
|
self.mapped = Some(MBC3Device::RealTimeClock);
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
@@ -356,11 +320,11 @@ impl MBCIo for MBC3 {
|
|||||||
}
|
}
|
||||||
self.prev_latch_write = Some(byte);
|
self.prev_latch_write = Some(byte);
|
||||||
}
|
}
|
||||||
0xA000..=0xBFFF => match self.currently_mapped {
|
0xA000..=0xBFFF => match self.mapped {
|
||||||
Some(MBC3Device::ExternalRam) if self.devices_enabled => {
|
Some(MBC3Device::ExternalRam) if self.devs_enabled => {
|
||||||
self.ram[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte
|
self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte
|
||||||
}
|
}
|
||||||
Some(MBC3Device::RealTimeClock) if self.devices_enabled => {
|
Some(MBC3Device::RealTimeClock) if self.devs_enabled => {
|
||||||
todo!("Write to RTC")
|
todo!("Write to RTC")
|
||||||
}
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
@@ -371,7 +335,127 @@ impl MBCIo for MBC3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct NoMBC {}
|
struct MBC5 {
|
||||||
|
/// 9-bit number
|
||||||
|
rom_bank: u16,
|
||||||
|
/// 4-bit number
|
||||||
|
ram_bank: u8,
|
||||||
|
|
||||||
|
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 {
|
impl MBCIo for NoMBC {
|
||||||
fn handle_read(&self, addr: u16) -> MBCResult {
|
fn handle_read(&self, addr: u16) -> MBCResult {
|
||||||
@@ -399,9 +483,12 @@ enum MBCKind {
|
|||||||
None,
|
None,
|
||||||
MBC1,
|
MBC1,
|
||||||
MBC1WithBattery,
|
MBC1WithBattery,
|
||||||
MBC5,
|
MBC2,
|
||||||
MBC3WithBattery,
|
MBC2WithBattery,
|
||||||
MBC3,
|
MBC3,
|
||||||
|
MBC3WithBattery,
|
||||||
|
MBC5,
|
||||||
|
MBC5WithBattery,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for MBCKind {
|
impl Default for MBCKind {
|
||||||
@@ -410,27 +497,27 @@ impl Default for MBCKind {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
enum RamSize {
|
enum RamSize {
|
||||||
None = 0x00,
|
None = 0x00,
|
||||||
_2KB = 0x01,
|
Unused = 0x01,
|
||||||
_8KB = 0x02,
|
One = 0x02,
|
||||||
_32KB = 0x03, // Split into 4 RAM banks
|
Four = 0x03,
|
||||||
_128KB = 0x04, // Split into 16 RAM banks
|
Sixteen = 0x04,
|
||||||
_64KB = 0x05, // Split into 8 RAm Banks
|
Eight = 0x05,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RamSize {
|
impl RamSize {
|
||||||
fn len(&self) -> u32 {
|
fn capacity(&self) -> usize {
|
||||||
use RamSize::*;
|
use RamSize::*;
|
||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
None => 0,
|
None => 0,
|
||||||
_2KB => 2_048,
|
Unused => 0x800,
|
||||||
_8KB => 8_192,
|
One => 0x2000,
|
||||||
_32KB => 32_768,
|
Four => 0x8000,
|
||||||
_128KB => 131_072,
|
Sixteen => 0x20000,
|
||||||
_64KB => 65_536,
|
Eight => 0x10000,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -447,63 +534,49 @@ impl From<u8> for RamSize {
|
|||||||
|
|
||||||
match byte {
|
match byte {
|
||||||
0x00 => None,
|
0x00 => None,
|
||||||
0x01 => _2KB,
|
0x01 => Unused,
|
||||||
0x02 => _8KB,
|
0x02 => One,
|
||||||
0x03 => _32KB,
|
0x03 => Four,
|
||||||
0x04 => _128KB,
|
0x04 => Sixteen,
|
||||||
0x05 => _64KB,
|
0x05 => Eight,
|
||||||
_ => unreachable!("{:#04X} is not a valid value for RAMSize", byte),
|
_ => unreachable!("{:#04X} is not a valid value for RAMSize", byte),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum BankCount {
|
enum RomSize {
|
||||||
None = 0x00, // 32KB (also called Two)
|
None = 0x00, // 32KB (also called Two)
|
||||||
Four = 0x01, // 64KB
|
Four = 0x01, // 64KB
|
||||||
Eight = 0x02, // 128KB
|
Eight = 0x02, // 128KB
|
||||||
Sixteen = 0x03, // 256KB
|
Sixteen = 0x03, // 256KB
|
||||||
ThirtyTwo = 0x04, // 512KB
|
ThirtyTwo = 0x04, // 512KB
|
||||||
SixtyFour = 0x05, // 1MB
|
SixtyFour = 0x05, // 1MB
|
||||||
OneHundredTwentyEight = 0x06, // 2MB
|
OneTwentyEight = 0x06, // 2MB
|
||||||
TwoHundredFiftySix = 0x07, // 4MB
|
TwoFiftySix = 0x07, // 4MB
|
||||||
FiveHundredTwelve = 0x08, // 8MB
|
FiveHundredTwelve = 0x08, // 8MB
|
||||||
SeventyTwo = 0x52, // 1.1MB
|
SeventyTwo = 0x52, // 1.1MB
|
||||||
Eighty = 0x53, // 1.2MB
|
Eighty = 0x53, // 1.2MB
|
||||||
NinetySix = 0x54, // 1.5MB
|
NinetySix = 0x54, // 1.5MB
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for BankCount {
|
impl RomSize {
|
||||||
fn default() -> Self {
|
|
||||||
Self::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BankCount {
|
|
||||||
// https://hacktix.github.io/GBEDG/mbcs/#rom-size
|
// https://hacktix.github.io/GBEDG/mbcs/#rom-size
|
||||||
fn size(&self) -> u32 {
|
fn capacity(&self) -> usize {
|
||||||
use BankCount::*;
|
use RomSize::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
None => 32_768,
|
SeventyTwo => 0x120000,
|
||||||
Four => 65_536,
|
Eighty => 0x140000,
|
||||||
Eight => 131_072,
|
NinetySix => 0x180000,
|
||||||
Sixteen => 262_144,
|
_ => 0x8000 << *self as u8,
|
||||||
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,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<u8> for BankCount {
|
impl From<u8> for RomSize {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
use BankCount::*;
|
use RomSize::*;
|
||||||
|
|
||||||
match byte {
|
match byte {
|
||||||
0x00 => None,
|
0x00 => None,
|
||||||
@@ -512,8 +585,8 @@ impl From<u8> for BankCount {
|
|||||||
0x03 => Sixteen,
|
0x03 => Sixteen,
|
||||||
0x04 => ThirtyTwo,
|
0x04 => ThirtyTwo,
|
||||||
0x05 => SixtyFour,
|
0x05 => SixtyFour,
|
||||||
0x06 => OneHundredTwentyEight,
|
0x06 => OneTwentyEight,
|
||||||
0x07 => TwoHundredFiftySix,
|
0x07 => TwoFiftySix,
|
||||||
0x08 => FiveHundredTwelve,
|
0x08 => FiveHundredTwelve,
|
||||||
0x52 => SeventyTwo,
|
0x52 => SeventyTwo,
|
||||||
0x53 => Eighty,
|
0x53 => Eighty,
|
||||||
@@ -531,6 +604,6 @@ impl std::fmt::Debug for Box<dyn MBCIo> {
|
|||||||
|
|
||||||
impl Default for Box<dyn MBCIo> {
|
impl Default for Box<dyn MBCIo> {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Box::new(MBC1::default())
|
Box::new(NoMBC)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
39
src/cpu.rs
39
src/cpu.rs
@@ -1,10 +1,7 @@
|
|||||||
use crate::apu::Apu;
|
use crate::bus::{Bus, BusIo, BOOT_SIZE};
|
||||||
use crate::bus::{Bus, BusIo};
|
|
||||||
use crate::instruction::cycle::Cycle;
|
|
||||||
use crate::instruction::Instruction;
|
use crate::instruction::Instruction;
|
||||||
use crate::interrupt::{InterruptEnable, InterruptFlag};
|
use crate::interrupt::{InterruptEnable, InterruptFlag};
|
||||||
use crate::joypad::Joypad;
|
use crate::Cycle;
|
||||||
use crate::ppu::Ppu;
|
|
||||||
use bitfield::bitfield;
|
use bitfield::bitfield;
|
||||||
use std::fmt::{Display, Formatter, Result as FmtResult};
|
use std::fmt::{Display, Formatter, Result as FmtResult};
|
||||||
|
|
||||||
@@ -18,7 +15,7 @@ pub struct Cpu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Cpu {
|
impl Cpu {
|
||||||
pub fn new() -> Self {
|
pub(crate) fn without_boot() -> Self {
|
||||||
Self {
|
Self {
|
||||||
reg: Registers {
|
reg: Registers {
|
||||||
a: 0x01,
|
a: 0x01,
|
||||||
@@ -36,11 +33,11 @@ impl Cpu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn boot_new(path: &str) -> anyhow::Result<Self> {
|
pub(crate) fn with_boot(rom: [u8; BOOT_SIZE]) -> Self {
|
||||||
Ok(Self {
|
Self {
|
||||||
bus: Bus::with_boot(path)?,
|
bus: Bus::with_boot(rom),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn ime(&self) -> ImeState {
|
pub(crate) fn ime(&self) -> ImeState {
|
||||||
@@ -72,14 +69,6 @@ impl Cpu {
|
|||||||
_ => None,
|
_ => 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 {
|
impl Cpu {
|
||||||
@@ -133,7 +122,7 @@ impl Cpu {
|
|||||||
self.bus.clock();
|
self.bus.clock();
|
||||||
|
|
||||||
let elapsed = match kind {
|
let elapsed = match kind {
|
||||||
ImeEnabled | NonePending => Cycle::new(4),
|
ImeEnabled | NonePending => 4,
|
||||||
SomePending => todo!("Implement HALT bug"),
|
SomePending => todo!("Implement HALT bug"),
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -167,16 +156,12 @@ impl BusIo for Cpu {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Cpu {
|
impl Cpu {
|
||||||
pub fn ppu(&mut self) -> &Ppu {
|
pub(crate) fn bus(&self) -> &Bus {
|
||||||
&self.bus.ppu
|
&self.bus
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn apu_mut(&mut self) -> &mut Apu {
|
pub(crate) fn bus_mut(&mut self) -> &mut Bus {
|
||||||
&mut self.bus.apu
|
&mut self.bus
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn joypad_mut(&mut self) -> &mut Joypad {
|
|
||||||
&mut self.bus.joypad
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_ei(&mut self) {
|
fn handle_ei(&mut self) {
|
||||||
|
156
src/emu.rs
156
src/emu.rs
@@ -1,73 +1,121 @@
|
|||||||
use crate::cpu::Cpu as SM83;
|
use crate::apu::gen::SampleProducer;
|
||||||
use crate::instruction::cycle::Cycle;
|
use crate::cpu::Cpu;
|
||||||
use crate::joypad;
|
use crate::joypad::{self, Joypad};
|
||||||
use crate::ppu::Ppu;
|
use crate::{Cycle, GB_HEIGHT, GB_WIDTH};
|
||||||
use anyhow::Result;
|
|
||||||
use gilrs::Gilrs;
|
use gilrs::Gilrs;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use winit_input_helper::WinitInputHelper;
|
use winit_input_helper::WinitInputHelper;
|
||||||
|
|
||||||
pub const SM83_CYCLE_TIME: Duration = Duration::from_nanos(1_000_000_000 / SM83_CLOCK_SPEED);
|
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
|
pub(crate) const SM83_CLOCK_SPEED: u64 = 0x40_0000; // Hz which is 4.194304Mhz
|
||||||
const DEFAULT_TITLE: &str = "DMG-01 Emulator";
|
const DEFAULT_TITLE: &str = "DMG-01 Emulator";
|
||||||
const GAMEPAD_ENABLED: bool = true;
|
|
||||||
|
|
||||||
pub fn init(boot_path: Option<&str>, rom_path: &str) -> Result<SM83> {
|
pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: &WinitInputHelper) -> Cycle {
|
||||||
let mut cpu = match boot_path {
|
let mut elapsed = 0;
|
||||||
Some(path) => SM83::boot_new(path)?,
|
|
||||||
None => SM83::new(),
|
|
||||||
};
|
|
||||||
|
|
||||||
eprintln!("Initialized GB Emulator");
|
|
||||||
|
|
||||||
cpu.load_cartridge(rom_path)?;
|
|
||||||
Ok(cpu)
|
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
target: Cycle,
|
|
||||||
) -> Cycle {
|
|
||||||
let mut elapsed = Cycle::new(0);
|
|
||||||
|
|
||||||
if GAMEPAD_ENABLED {
|
|
||||||
if let Some(event) = gamepad.next_event() {
|
if let Some(event) = gamepad.next_event() {
|
||||||
joypad::handle_gamepad_input(game_boy.joypad_mut(), event);
|
joypad::handle_gamepad_input(emu.joyp_mut(), event);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
joypad::handle_keyboard_input(game_boy.joypad_mut(), input);
|
joypad::handle_keyboard_input(emu.joyp_mut(), key);
|
||||||
while elapsed < target {
|
|
||||||
elapsed += game_boy.step();
|
|
||||||
}
|
|
||||||
|
|
||||||
elapsed
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn run_frame(game_boy: &mut SM83, gamepad: &mut Gilrs, input: &WinitInputHelper) -> Cycle {
|
|
||||||
let mut elapsed = Cycle::new(0);
|
|
||||||
|
|
||||||
if GAMEPAD_ENABLED {
|
|
||||||
if let Some(event) = gamepad.next_event() {
|
|
||||||
joypad::handle_gamepad_input(game_boy.joypad_mut(), event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
joypad::handle_keyboard_input(game_boy.joypad_mut(), input);
|
|
||||||
while elapsed < CYCLES_IN_FRAME {
|
while elapsed < CYCLES_IN_FRAME {
|
||||||
elapsed += game_boy.step();
|
elapsed += emu.step();
|
||||||
}
|
}
|
||||||
|
|
||||||
elapsed
|
elapsed
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn draw(ppu: &Ppu, frame: &mut [u8]) {
|
pub fn draw_frame(emu: &Emulator, buf: &mut [u8; GB_HEIGHT * GB_WIDTH * 4]) {
|
||||||
ppu.copy_to_gui(frame);
|
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(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn step(&mut self) -> Cycle {
|
||||||
|
self.cpu.step()
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
|||||||
pub use apu::gen::AudioSPSC;
|
pub use apu::gen::AudioSPSC;
|
||||||
pub use instruction::cycle::Cycle;
|
pub type Cycle = u64;
|
||||||
|
|
||||||
pub const GB_WIDTH: usize = 160;
|
pub const GB_WIDTH: usize = 160;
|
||||||
pub const GB_HEIGHT: usize = 144;
|
pub const GB_HEIGHT: usize = 144;
|
||||||
|
102
src/main.rs
102
src/main.rs
@@ -1,17 +1,20 @@
|
|||||||
|
use std::convert::TryInto;
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
|
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
|
||||||
|
use gb::emu::build::EmulatorBuilder;
|
||||||
use gb::{AudioSPSC, Cycle, GB_HEIGHT, GB_WIDTH};
|
use gb::{AudioSPSC, Cycle, GB_HEIGHT, GB_WIDTH};
|
||||||
use gilrs::Gilrs;
|
use gilrs::Gilrs;
|
||||||
use pixels::{PixelsBuilder, SurfaceTexture};
|
use pixels::{PixelsBuilder, SurfaceTexture};
|
||||||
use rodio::{OutputStream, Sink};
|
use rodio::{OutputStream, Sink};
|
||||||
use std::time::{Duration, Instant};
|
use winit::dpi::{LogicalSize, PhysicalSize};
|
||||||
use winit::dpi::LogicalSize;
|
|
||||||
use winit::event::{Event, VirtualKeyCode};
|
use winit::event::{Event, VirtualKeyCode};
|
||||||
use winit::event_loop::{ControlFlow, EventLoop};
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
use winit::window::{Window, WindowBuilder};
|
use winit::window::{Window, WindowBuilder};
|
||||||
use winit_input_helper::WinitInputHelper;
|
use winit_input_helper::WinitInputHelper;
|
||||||
|
|
||||||
const WINDOW_SCALE: f64 = 2.0;
|
const WINDOW_SCALE: usize = 3;
|
||||||
|
const AUDIO_ENABLED: bool = false;
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let app = App::new(crate_name!())
|
let app = App::new(crate_name!())
|
||||||
@@ -38,13 +41,15 @@ fn main() -> Result<()> {
|
|||||||
)
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
let rom_path = m
|
let mut emu_build =
|
||||||
.value_of("rom")
|
EmulatorBuilder::new().with_cart(m.value_of("rom").expect("ROM path provided"))?;
|
||||||
.expect("Required value 'rom' was provided");
|
|
||||||
|
|
||||||
let mut game_boy =
|
if let Some(path) = m.value_of("boot") {
|
||||||
gb::emu::init(m.value_of("boot"), rom_path).expect("Initialize DMG-01 Emulator");
|
emu_build = emu_build.with_boot(path)?;
|
||||||
let rom_title = gb::emu::rom_title(&game_boy);
|
}
|
||||||
|
|
||||||
|
let mut emu = emu_build.finish();
|
||||||
|
let rom_title = emu.title();
|
||||||
|
|
||||||
let mut gamepad = Gilrs::new().expect("Initialize Controller Support");
|
let mut gamepad = Gilrs::new().expect("Initialize Controller Support");
|
||||||
|
|
||||||
@@ -63,19 +68,25 @@ fn main() -> Result<()> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Initialize Audio
|
// Initialize Audio
|
||||||
// let spsc: AudioSPSC<f32> = Default::default();
|
let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio");
|
||||||
// let (prod, cons) = spsc.init();
|
|
||||||
// let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio");
|
|
||||||
// let sink = Sink::try_new(&stream_handle)?;
|
|
||||||
// sink.append(cons);
|
|
||||||
// game_boy.apu_mut().set_producer(prod);
|
|
||||||
|
|
||||||
// std::thread::spawn(move || {
|
if AUDIO_ENABLED {
|
||||||
// sink.sleep_until_end();
|
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.sleep_until_end();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let mut start = Instant::now();
|
|
||||||
let frame_time = Duration::from_secs_f64(1.0 / 59.73); // 59.73 Hz on Host
|
|
||||||
let mut cycle_count: Cycle = Default::default();
|
let mut cycle_count: Cycle = Default::default();
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
@@ -100,57 +111,34 @@ fn main() -> Result<()> {
|
|||||||
pixels.resize_surface(size.width, size.height);
|
pixels.resize_surface(size.width, size.height);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut diff = Instant::now() - start;
|
cycle_count += gb::emu::run_frame(&mut emu, &mut gamepad, &input);
|
||||||
while diff.subsec_nanos() < frame_time.subsec_nanos() {
|
|
||||||
if cycle_count < gb::emu::CYCLES_IN_FRAME {
|
|
||||||
cycle_count += gb::emu::run_frame(&mut game_boy, &mut gamepad, &input);
|
|
||||||
}
|
|
||||||
|
|
||||||
diff = Instant::now() - start;
|
|
||||||
}
|
|
||||||
start = Instant::now();
|
|
||||||
|
|
||||||
if cycle_count >= gb::emu::CYCLES_IN_FRAME {
|
if cycle_count >= gb::emu::CYCLES_IN_FRAME {
|
||||||
cycle_count %= gb::emu::CYCLES_IN_FRAME;
|
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();
|
window.request_redraw();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(not(windows))]
|
|
||||||
fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> {
|
fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> {
|
||||||
let size = LogicalSize::new(
|
let logical = LogicalSize::new(GB_WIDTH as f64, GB_HEIGHT as f64);
|
||||||
(GB_WIDTH as f64) * WINDOW_SCALE,
|
let physical = PhysicalSize::new(
|
||||||
(GB_HEIGHT as f64) * WINDOW_SCALE,
|
(GB_WIDTH * WINDOW_SCALE) as f32,
|
||||||
|
(GB_HEIGHT * WINDOW_SCALE) as f32,
|
||||||
);
|
);
|
||||||
|
|
||||||
Ok(WindowBuilder::new()
|
Ok(WindowBuilder::new()
|
||||||
.with_title(title)
|
.with_title(title)
|
||||||
.with_inner_size(size)
|
.with_min_inner_size(logical)
|
||||||
.with_min_inner_size(size)
|
.with_inner_size(physical)
|
||||||
.with_resizable(true)
|
.with_resizable(true)
|
||||||
.with_decorations(true)
|
|
||||||
.with_transparent(false)
|
|
||||||
.build(event_loop)?)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(windows)]
|
|
||||||
fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> {
|
|
||||||
use winit::platform::windows::WindowBuilderExtWindows;
|
|
||||||
|
|
||||||
let size = LogicalSize::new(
|
|
||||||
(GB_WIDTH as f64) * WINDOW_SCALE,
|
|
||||||
(GB_HEIGHT as f64) * WINDOW_SCALE,
|
|
||||||
);
|
|
||||||
Ok(WindowBuilder::new()
|
|
||||||
.with_title(title)
|
|
||||||
.with_inner_size(size)
|
|
||||||
.with_min_inner_size(size)
|
|
||||||
.with_resizable(true)
|
|
||||||
.with_decorations(true)
|
|
||||||
.with_transparent(false)
|
|
||||||
.with_drag_and_drop(false)
|
|
||||||
.build(event_loop)?)
|
.build(event_loop)?)
|
||||||
}
|
}
|
||||||
|
18
src/ppu.rs
18
src/ppu.rs
@@ -1,5 +1,5 @@
|
|||||||
use crate::bus::BusIo;
|
use crate::bus::BusIo;
|
||||||
use crate::instruction::cycle::Cycle;
|
use crate::Cycle;
|
||||||
use crate::GB_HEIGHT;
|
use crate::GB_HEIGHT;
|
||||||
use crate::GB_WIDTH;
|
use crate::GB_WIDTH;
|
||||||
use dma::DirectMemoryAccess;
|
use dma::DirectMemoryAccess;
|
||||||
@@ -79,7 +79,7 @@ impl Ppu {
|
|||||||
|
|
||||||
match self.stat.mode() {
|
match self.stat.mode() {
|
||||||
PpuMode::OamScan => {
|
PpuMode::OamScan => {
|
||||||
if self.cycle >= 80.into() {
|
if self.cycle >= 80 {
|
||||||
self.stat.set_mode(PpuMode::Drawing);
|
self.stat.set_mode(PpuMode::Drawing);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -88,7 +88,7 @@ impl Ppu {
|
|||||||
PpuMode::Drawing => {
|
PpuMode::Drawing => {
|
||||||
if self.ctrl.lcd_enabled() {
|
if self.ctrl.lcd_enabled() {
|
||||||
// Only Draw when the LCD Is Enabled
|
// Only Draw when the LCD Is Enabled
|
||||||
self.draw(self.cycle.into());
|
self.draw(self.cycle);
|
||||||
} else {
|
} else {
|
||||||
self.reset();
|
self.reset();
|
||||||
}
|
}
|
||||||
@@ -125,7 +125,7 @@ impl Ppu {
|
|||||||
PpuMode::HBlank => {
|
PpuMode::HBlank => {
|
||||||
// This mode will always end at 456 cycles
|
// This mode will always end at 456 cycles
|
||||||
|
|
||||||
if self.cycle >= 456.into() {
|
if self.cycle >= 456 {
|
||||||
self.cycle %= 456;
|
self.cycle %= 456;
|
||||||
self.pos.line_y += 1;
|
self.pos.line_y += 1;
|
||||||
|
|
||||||
@@ -167,7 +167,7 @@ impl Ppu {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
PpuMode::VBlank => {
|
PpuMode::VBlank => {
|
||||||
if self.cycle > 456.into() {
|
if self.cycle > 456 {
|
||||||
self.cycle %= 456;
|
self.cycle %= 456;
|
||||||
self.pos.line_y += 1;
|
self.pos.line_y += 1;
|
||||||
|
|
||||||
@@ -228,7 +228,7 @@ impl Ppu {
|
|||||||
self.scan_state.next();
|
self.scan_state.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(&mut self, _cycle: u32) {
|
fn draw(&mut self, _cycle: Cycle) {
|
||||||
use FetcherState::*;
|
use FetcherState::*;
|
||||||
|
|
||||||
let mut iter = self.obj_buffer.iter_mut();
|
let mut iter = self.obj_buffer.iter_mut();
|
||||||
@@ -418,8 +418,8 @@ impl Ppu {
|
|||||||
self.frame_buf.swap_with_slice(&mut blank);
|
self.frame_buf.swap_with_slice(&mut blank);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn copy_to_gui(&self, frame: &mut [u8]) {
|
pub(crate) fn frame_buf(&self) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] {
|
||||||
frame.copy_from_slice(self.frame_buf.as_ref());
|
&self.frame_buf
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clock_fifo(&mut self) -> Option<GrayShade> {
|
fn clock_fifo(&mut self) -> Option<GrayShade> {
|
||||||
@@ -470,7 +470,7 @@ impl Default for Ppu {
|
|||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
vram: Box::new([0u8; VRAM_SIZE]),
|
vram: Box::new([0u8; VRAM_SIZE]),
|
||||||
cycle: Cycle::new(0),
|
cycle: Default::default(),
|
||||||
frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]),
|
frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]),
|
||||||
int: Default::default(),
|
int: Default::default(),
|
||||||
ctrl: Default::default(),
|
ctrl: Default::default(),
|
||||||
|
@@ -1,4 +1,4 @@
|
|||||||
use crate::instruction::cycle::Cycle;
|
use crate::Cycle;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct DirectMemoryAccess {
|
pub(crate) struct DirectMemoryAccess {
|
||||||
@@ -56,7 +56,7 @@ impl DirectMemoryAccess {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn reset(&mut self) {
|
fn reset(&mut self) {
|
||||||
self.cycle = Cycle::new(0);
|
self.cycle = 0;
|
||||||
self.state = DmaState::Disabled;
|
self.state = DmaState::Disabled;
|
||||||
self.start.0 = None;
|
self.start.0 = None;
|
||||||
}
|
}
|
||||||
|
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,
|
||||||
|
}
|
33
src/timer.rs
33
src/timer.rs
@@ -21,17 +21,15 @@ impl Timer {
|
|||||||
use State::*;
|
use State::*;
|
||||||
use TimerSpeed::*;
|
use TimerSpeed::*;
|
||||||
|
|
||||||
if let TIMAOverflow(step) | AbortedTIMAOverflow(step) = self.state {
|
match self.state {
|
||||||
if step < 4 {
|
TIMAOverflow(_) | AbortedTIMAOverflow(_) => self.next(),
|
||||||
self.state = TIMAOverflow(step + 1);
|
LoadTMA => {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.state == TIMAOverflow(step) {
|
|
||||||
self.counter = self.modulo;
|
self.counter = self.modulo;
|
||||||
self.interrupt = true;
|
self.interrupt = true;
|
||||||
|
|
||||||
|
self.next();
|
||||||
}
|
}
|
||||||
self.state = Normal;
|
Normal => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.divider = self.divider.wrapping_add(1);
|
self.divider = self.divider.wrapping_add(1);
|
||||||
@@ -67,14 +65,12 @@ impl Timer {
|
|||||||
use State::*;
|
use State::*;
|
||||||
|
|
||||||
match self.state {
|
match self.state {
|
||||||
Normal => self.counter = byte,
|
Normal | AbortedTIMAOverflow(_) => self.counter = byte,
|
||||||
TIMAOverflow(step) => {
|
TIMAOverflow(step) => {
|
||||||
if step < 4 {
|
|
||||||
self.counter = byte;
|
self.counter = byte;
|
||||||
self.state = AbortedTIMAOverflow(step);
|
self.state = AbortedTIMAOverflow(step);
|
||||||
}
|
}
|
||||||
}
|
LoadTMA => {}
|
||||||
AbortedTIMAOverflow(_) => self.counter = byte,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +90,18 @@ impl Timer {
|
|||||||
self.state = State::TIMAOverflow(0);
|
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 {
|
impl Default for Timer {
|
||||||
@@ -173,4 +181,5 @@ enum State {
|
|||||||
TIMAOverflow(u8),
|
TIMAOverflow(u8),
|
||||||
AbortedTIMAOverflow(u8),
|
AbortedTIMAOverflow(u8),
|
||||||
Normal,
|
Normal,
|
||||||
|
LoadTMA,
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user