Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Rekai Nyangadzayi Musuka | 9baa15050e |
|
@ -1,9 +1,8 @@
|
||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"rust-lang.rust-analyzer",
|
"matklad.rust-analyzer",
|
||||||
"tamasfe.even-better-toml",
|
"tamasfe.even-better-toml",
|
||||||
"serayuzgur.crates",
|
"serayuzgur.crates",
|
||||||
"vadimcn.vscode-lldb",
|
"vadimcn.vscode-lldb",
|
||||||
"donaldhays.rgbds-z80"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
File diff suppressed because it is too large
Load Diff
28
Cargo.toml
28
Cargo.toml
|
@ -2,27 +2,21 @@
|
||||||
name = "gb"
|
name = "gb"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
authors = ["Rekai Musuka <rekai@musuka.dev>"]
|
authors = ["Rekai Musuka <rekai@musuka.dev>"]
|
||||||
edition = "2021"
|
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
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0"
|
anyhow = "^1.0"
|
||||||
bitfield = "0.17"
|
bitfield = "^0.13"
|
||||||
clap = { version = "4.0", features = ["cargo"] }
|
clap = "^2.33"
|
||||||
gilrs = "0.11"
|
gilrs = "^0.8"
|
||||||
winit = "0.28"
|
pixels = "^0.6"
|
||||||
egui = "0.21"
|
winit = "^0.25"
|
||||||
egui_wgpu_backend = "0.22"
|
winit_input_helper = "^0.10"
|
||||||
egui_winit_platform = "0.18"
|
rodio = "^0.14"
|
||||||
pollster = "0.3"
|
rtrb = "^0.1.4"
|
||||||
rodio = "0.19"
|
|
||||||
rtrb = "0.3"
|
|
||||||
directories-next = "2.0"
|
|
||||||
tracing = "0.1"
|
|
||||||
tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] }
|
|
||||||
thiserror = "1.0"
|
|
||||||
once_cell = "1.10"
|
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
debug = true
|
debug = true
|
||||||
|
|
47
README.md
47
README.md
|
@ -1,43 +1,14 @@
|
||||||
# Rekai's Gameboy Emulator
|
# Rekai's Gameboy Emulator
|
||||||
|
[![Build Status](https://ci.paoda.moe/api/badges/paoda/gb/status.svg)](https://ci.paoda.moe/paoda/gb)
|
||||||
|
|
||||||
### Status
|
### Status
|
||||||
* From [Blargg Test ROMs](https://github.com/L-P/blargg-test-roms/)
|
* Passes Blargg's cpu_instrs Test
|
||||||
* [x] cpu_instrs
|
* Renders Background & Window Tiles
|
||||||
* [x] instr_timing
|
* Implements a PPU FIFO
|
||||||
* [x] mem_timing
|
|
||||||
* [x] mem_timing-2
|
|
||||||
* [ ] dmg_sound (partial)
|
|
||||||
* [x] [dmg-acid2](https://github.com/mattcurrie/dmg-acid2)
|
|
||||||
* From [mooneye-gb](https://github.com/Gekkio/mooneye-gb):
|
|
||||||
* Cartridges:
|
|
||||||
* [x] MBC1
|
|
||||||
* [ ] MBC1M
|
|
||||||
* [x] MBC2
|
|
||||||
* [x] MBC5
|
|
||||||
* Implements a cycle-accurate PPU FIFO
|
|
||||||
* Doesn't \*exactly\* work just yet
|
|
||||||
|
|
||||||
Supports: ROM-only, MBC1, MBC2, MBC3 and MBC5 games.
|
|
||||||
|
|
||||||
|
|
||||||
### Compiling
|
### Notes
|
||||||
This project was last successfully built on [Rust 1.64.0](https://github.com/rust-lang/rust/tree/1.64.0)
|
* [gameboy-logs](https://github.com/wheremyfoodat/Gameboy-logs) suggests that there are still some underlying problems with the cpu implementation
|
||||||
|
* The Sprite FIFO does not work as expected yet
|
||||||
1. `git clone https://github.com/paoda/gb`
|
* Sound is neither emulated nor stubbed. Upon writing / reading to a APU related register the emulator will panic.
|
||||||
2. `cd gb`
|
* Code cleanup is pending completion of some minimum viable product of the emulator
|
||||||
3. `cargo run --release`
|
|
||||||
|
|
||||||
### Controls
|
|
||||||
Controls are defined [here](https://github.com/paoda/gb/blob/85940c874460b9cb31bf9b8211bf7dda596d114a/src/joypad.rs#L114)
|
|
||||||
|
|
||||||
Key | Button
|
|
||||||
--- | ---
|
|
||||||
<kbd>X</kbd> | B
|
|
||||||
<kbd>Z</kbd> | A
|
|
||||||
<kbd>Enter</kbd> | START
|
|
||||||
<kbd>Shift</kbd> | SELECT
|
|
||||||
|
|
||||||
Then use the Arrow keys for the D-Pad
|
|
||||||
|
|
||||||
### Credits
|
|
||||||
The Boot ROM found in the `bin/` directory was made by [Optix](https://github.com/Hacktix) over [here](https://github.com/Hacktix/Bootix)
|
|
||||||
|
|
Binary file not shown.
326
src/apu.rs
326
src/apu.rs
|
@ -1,4 +1,3 @@
|
||||||
use crate::apu::gen::SAMPLE_RATE;
|
|
||||||
use crate::bus::BusIo;
|
use crate::bus::BusIo;
|
||||||
use crate::emu::SM83_CLOCK_SPEED;
|
use crate::emu::SM83_CLOCK_SPEED;
|
||||||
use gen::SampleProducer;
|
use gen::SampleProducer;
|
||||||
|
@ -6,8 +5,8 @@ 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 FSState};
|
use types::fs::{FrameSequencer, State as FrameSequencerState};
|
||||||
use types::{ChannelControl, NRx4, SoundOutput};
|
use types::{ChannelControl, SoundOutput};
|
||||||
|
|
||||||
pub mod gen;
|
pub mod gen;
|
||||||
mod types;
|
mod types;
|
||||||
|
@ -27,13 +26,11 @@ pub struct Apu {
|
||||||
/// Noise
|
/// Noise
|
||||||
ch4: Channel4,
|
ch4: Channel4,
|
||||||
|
|
||||||
fs: FrameSequencer,
|
sequencer: FrameSequencer,
|
||||||
div_prev: Option<u16>,
|
div_prev: Option<u16>,
|
||||||
|
|
||||||
pub(crate) prod: Option<SampleProducer<f32>>,
|
prod: Option<SampleProducer<f32>>,
|
||||||
sample_counter: u64,
|
sample_counter: u64,
|
||||||
|
|
||||||
cap: f32,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BusIo for Apu {
|
impl BusIo for Apu {
|
||||||
|
@ -57,7 +54,7 @@ impl BusIo for Apu {
|
||||||
0x26 => self.ctrl.status(self),
|
0x26 => self.ctrl.status(self),
|
||||||
0x30..=0x3F => self.ch3.read_byte(addr),
|
0x30..=0x3F => self.ch3.read_byte(addr),
|
||||||
_ => {
|
_ => {
|
||||||
tracing::warn!("Attempted read from {:#06X}", addr);
|
eprintln!("Read 0xFF from unused IO register {:#06X} [APU]", addr);
|
||||||
0xFF
|
0xFF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -69,26 +66,29 @@ impl BusIo for Apu {
|
||||||
0x11 if self.ctrl.enabled => self.ch1.set_duty(byte),
|
0x11 if self.ctrl.enabled => self.ch1.set_duty(byte),
|
||||||
0x12 if self.ctrl.enabled => self.ch1.set_envelope(byte),
|
0x12 if self.ctrl.enabled => self.ch1.set_envelope(byte),
|
||||||
0x13 if self.ctrl.enabled => self.ch1.set_freq_lo(byte),
|
0x13 if self.ctrl.enabled => self.ch1.set_freq_lo(byte),
|
||||||
0x14 if self.ctrl.enabled => self.ch1.set_freq_hi(&self.fs, byte),
|
0x14 if self.ctrl.enabled => self.ch1.set_freq_hi(byte),
|
||||||
0x16 if self.ctrl.enabled => self.ch2.set_duty(byte),
|
0x16 if self.ctrl.enabled => self.ch2.set_duty(byte),
|
||||||
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(&self.fs, byte),
|
0x19 if self.ctrl.enabled => self.ch2.set_freq_hi(byte),
|
||||||
0x1A if self.ctrl.enabled => self.ch3.set_dac_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),
|
||||||
0x1E if self.ctrl.enabled => self.ch3.set_freq_hi(&self.fs, byte),
|
0x1E if self.ctrl.enabled => self.ch3.set_freq_hi(byte),
|
||||||
0x20 if self.ctrl.enabled => self.ch4.set_len(byte),
|
0x20 if self.ctrl.enabled => self.ch4.set_len(byte),
|
||||||
0x21 if self.ctrl.enabled => self.ch4.set_envelope(byte),
|
0x21 if self.ctrl.enabled => self.ch4.set_envelope(byte),
|
||||||
0x22 if self.ctrl.enabled => self.ch4.set_poly(byte),
|
0x22 if self.ctrl.enabled => self.ch4.set_poly(byte),
|
||||||
0x23 if self.ctrl.enabled => self.ch4.set_frequency(&self.fs, byte),
|
0x23 if self.ctrl.enabled => self.ch4.set_frequency(byte),
|
||||||
0x24 if self.ctrl.enabled => self.ctrl.set_channel(byte),
|
0x24 if self.ctrl.enabled => self.ctrl.set_channel(byte),
|
||||||
0x25 if self.ctrl.enabled => self.ctrl.set_output(byte),
|
0x25 if self.ctrl.enabled => self.ctrl.set_output(byte),
|
||||||
0x26 => self.set_status(byte),
|
0x26 => self.set_status(byte),
|
||||||
0x30..=0x3F => self.ch3.write_byte(addr, byte),
|
0x30..=0x3F => self.ch3.write_byte(addr, byte),
|
||||||
_ if !self.ctrl.enabled => {}
|
_ if !self.ctrl.enabled => {}
|
||||||
_ => tracing::warn!("Attempted write of {:#04X} to {:#06X}", byte, addr),
|
_ => eprintln!(
|
||||||
|
"Wrote {:#04X} to unused IO register {:#06X} [APU]",
|
||||||
|
byte, addr
|
||||||
|
),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,9 +99,9 @@ impl Apu {
|
||||||
|
|
||||||
// Frame Sequencer (512Hz)
|
// Frame Sequencer (512Hz)
|
||||||
if self.is_falling_edge(12, div) {
|
if self.is_falling_edge(12, div) {
|
||||||
use FSState::*;
|
use FrameSequencerState::*;
|
||||||
|
|
||||||
match self.fs.state() {
|
match self.sequencer.state() {
|
||||||
Length => self.clock_length(),
|
Length => self.clock_length(),
|
||||||
LengthAndSweep => {
|
LengthAndSweep => {
|
||||||
self.clock_length();
|
self.clock_length();
|
||||||
|
@ -111,7 +111,7 @@ impl Apu {
|
||||||
Nothing => {}
|
Nothing => {}
|
||||||
}
|
}
|
||||||
|
|
||||||
self.fs.next();
|
self.sequencer.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.div_prev = Some(div);
|
self.div_prev = Some(div);
|
||||||
|
@ -124,33 +124,25 @@ impl Apu {
|
||||||
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(prod) = self.prod.as_mut() {
|
if let Some(ref mut prod) = self.prod {
|
||||||
if prod.available_blocking() {
|
if prod.available_blocking() {
|
||||||
// Sample the APU
|
// Sample the APU
|
||||||
|
|
||||||
let ch1_amplitude =
|
|
||||||
Self::high_pass(&mut self.cap, self.ch1.amplitude(), self.ch1.enabled);
|
|
||||||
let (left, right) = self.ctrl.out.ch1();
|
let (left, right) = self.ctrl.out.ch1();
|
||||||
let ch1_left = if left { ch1_amplitude } else { 0.0 };
|
let ch1_left = if left { self.ch1.amplitude() } else { 0.0 };
|
||||||
let ch1_right = if right { ch1_amplitude } else { 0.0 };
|
let ch1_right = if right { self.ch1.amplitude() } else { 0.0 };
|
||||||
|
|
||||||
let ch2_amplitude =
|
|
||||||
Self::high_pass(&mut self.cap, self.ch2.amplitude(), self.ch2.enabled);
|
|
||||||
let (left, right) = self.ctrl.out.ch2();
|
let (left, right) = self.ctrl.out.ch2();
|
||||||
let ch2_left = if left { ch2_amplitude } else { 0.0 };
|
let ch2_left = if left { self.ch2.amplitude() } else { 0.0 };
|
||||||
let ch2_right = if right { ch2_amplitude } else { 0.0 };
|
let ch2_right = if right { self.ch2.amplitude() } else { 0.0 };
|
||||||
|
|
||||||
let ch3_amplitude =
|
|
||||||
Self::high_pass(&mut self.cap, self.ch3.amplitude(), self.ch3.enabled);
|
|
||||||
let (left, right) = self.ctrl.out.ch3();
|
let (left, right) = self.ctrl.out.ch3();
|
||||||
let ch3_left = if left { ch3_amplitude } else { 0.0 };
|
let ch3_left = if left { self.ch3.amplitude() } else { 0.0 };
|
||||||
let ch3_right = if right { ch3_amplitude } else { 0.0 };
|
let ch3_right = if right { self.ch3.amplitude() } else { 0.0 };
|
||||||
|
|
||||||
let ch4_amplitude =
|
|
||||||
Self::high_pass(&mut self.cap, self.ch4.amplitude(), self.ch4.enabled);
|
|
||||||
let (left, right) = self.ctrl.out.ch4();
|
let (left, right) = self.ctrl.out.ch4();
|
||||||
let ch4_left = if left { ch4_amplitude } else { 0.0 };
|
let ch4_left = if left { self.ch4.amplitude() } else { 0.0 };
|
||||||
let ch4_right = if right { 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;
|
||||||
|
@ -172,7 +164,7 @@ impl Apu {
|
||||||
|
|
||||||
if self.ctrl.enabled {
|
if self.ctrl.enabled {
|
||||||
// Frame Sequencer reset to Step 0
|
// Frame Sequencer reset to Step 0
|
||||||
self.fs.reset();
|
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;
|
||||||
|
@ -188,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();
|
||||||
|
@ -211,8 +207,8 @@ impl Apu {
|
||||||
self.ch4.poly = Default::default();
|
self.ch4.poly = Default::default();
|
||||||
self.ch4.freq = Default::default();
|
self.ch4.freq = Default::default();
|
||||||
|
|
||||||
self.ctrl.channel = ChannelControl(0);
|
self.ctrl.channel = Default::default();
|
||||||
self.ctrl.out = SoundOutput(0);
|
self.ctrl.out = Default::default();
|
||||||
|
|
||||||
// Disable the Channels
|
// Disable the Channels
|
||||||
self.ch1.enabled = Default::default();
|
self.ch1.enabled = Default::default();
|
||||||
|
@ -221,13 +217,25 @@ impl Apu {
|
||||||
self.ch4.enabled = Default::default();
|
self.ch4.enabled = Default::default();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_length(freq: &impl NRx4, counter: &mut u16, enabled: &mut bool) {
|
fn process_length(freq_hi: &FrequencyHigh, length_timer: &mut u16, enabled: &mut bool) {
|
||||||
if freq.length_enable() && *counter > 0 {
|
if freq_hi.length_disable() && *length_timer > 0 {
|
||||||
*counter -= 1;
|
*length_timer -= 1;
|
||||||
|
|
||||||
// Check in this scope ensures (only) the above subtraction
|
// Check in this scope ensures (only) the above subtraction
|
||||||
// made length_timer 0
|
// made length_timer 0
|
||||||
if *counter == 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;
|
*enabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,25 +244,25 @@ impl Apu {
|
||||||
fn clock_length(&mut self) {
|
fn clock_length(&mut self) {
|
||||||
Self::process_length(
|
Self::process_length(
|
||||||
&self.ch1.freq_hi,
|
&self.ch1.freq_hi,
|
||||||
&mut self.ch1.length_counter,
|
&mut self.ch1.length_timer,
|
||||||
&mut self.ch1.enabled,
|
&mut self.ch1.enabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
Self::process_length(
|
Self::process_length(
|
||||||
&self.ch2.freq_hi,
|
&self.ch2.freq_hi,
|
||||||
&mut self.ch2.length_counter,
|
&mut self.ch2.length_timer,
|
||||||
&mut self.ch2.enabled,
|
&mut self.ch2.enabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
Self::process_length(
|
Self::process_length(
|
||||||
&self.ch3.freq_hi,
|
&self.ch3.freq_hi,
|
||||||
&mut self.ch3.length_counter,
|
&mut self.ch3.length_timer,
|
||||||
&mut self.ch3.enabled,
|
&mut self.ch3.enabled,
|
||||||
);
|
);
|
||||||
|
|
||||||
Self::process_length(
|
Self::ch4_process_length(
|
||||||
&self.ch4.freq,
|
&self.ch4.freq,
|
||||||
&mut self.ch4.length_counter,
|
&mut self.ch4.length_timer,
|
||||||
&mut self.ch4.enabled,
|
&mut self.ch4.enabled,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -271,7 +279,7 @@ impl Apu {
|
||||||
if self.ch1.sweep_enabled && period != 0 {
|
if self.ch1.sweep_enabled && period != 0 {
|
||||||
let new_freq = self.ch1.calc_sweep_freq();
|
let new_freq = self.ch1.calc_sweep_freq();
|
||||||
|
|
||||||
if new_freq <= 0x7FF && self.ch1.sweep.shift_count() != 0 {
|
if new_freq <= 2047 && self.ch1.sweep.shift_count() != 0 {
|
||||||
self.ch1.set_frequency(new_freq);
|
self.ch1.set_frequency(new_freq);
|
||||||
self.ch1.shadow_freq = new_freq;
|
self.ch1.shadow_freq = new_freq;
|
||||||
|
|
||||||
|
@ -329,22 +337,9 @@ impl Apu {
|
||||||
None => false,
|
None => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn high_pass(capacitor: &mut f32, input: f32, enabled: bool) -> f32 {
|
|
||||||
const CHARGE_FACTOR: f32 = 0.999958;
|
|
||||||
|
|
||||||
let mut output = 0.0;
|
|
||||||
if enabled {
|
|
||||||
output = input - *capacitor;
|
|
||||||
*capacitor =
|
|
||||||
input - output * CHARGE_FACTOR.powi(SM83_CLOCK_SPEED as i32 / SAMPLE_RATE as i32);
|
|
||||||
}
|
|
||||||
|
|
||||||
output
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct SoundControl {
|
pub(crate) struct SoundControl {
|
||||||
/// 0xFF24 | NR50 - Channel Control
|
/// 0xFF24 | NR50 - Channel Control
|
||||||
channel: ChannelControl,
|
channel: ChannelControl,
|
||||||
|
@ -354,16 +349,6 @@ pub(crate) struct SoundControl {
|
||||||
enabled: bool,
|
enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for SoundControl {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
channel: ChannelControl(0),
|
|
||||||
out: SoundOutput(0),
|
|
||||||
enabled: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl SoundControl {
|
impl SoundControl {
|
||||||
/// 0xFF24 | NR50 - Channel Control
|
/// 0xFF24 | NR50 - Channel Control
|
||||||
pub(crate) fn channel(&self) -> u8 {
|
pub(crate) fn channel(&self) -> u8 {
|
||||||
|
@ -423,7 +408,7 @@ pub(crate) struct Channel1 {
|
||||||
sweep_enabled: bool,
|
sweep_enabled: bool,
|
||||||
|
|
||||||
// Length Functionality
|
// Length Functionality
|
||||||
length_counter: u16,
|
length_timer: u16,
|
||||||
|
|
||||||
freq_timer: u16,
|
freq_timer: u16,
|
||||||
duty_pos: u8,
|
duty_pos: u8,
|
||||||
|
@ -450,11 +435,11 @@ impl Channel1 {
|
||||||
/// 0xFF11 | NR11 - Channel 1 Sound length / Wave pattern duty
|
/// 0xFF11 | NR11 - Channel 1 Sound length / Wave pattern duty
|
||||||
pub(crate) fn set_duty(&mut self, byte: u8) {
|
pub(crate) fn set_duty(&mut self, byte: u8) {
|
||||||
self.duty = byte.into();
|
self.duty = byte.into();
|
||||||
self.length_counter = 64 - self.duty.sound_length() as u16;
|
self.length_timer = 64 - self.duty.sound_length() as u16;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0xFF12 | NR12 - Channel 1 Volume Envelope
|
/// 0xFF12 | NR12 - Channel 1 Volume Envelope
|
||||||
pub(crate) fn envelope(&self) -> u8 {
|
pub fn envelope(&self) -> u8 {
|
||||||
u8::from(self.envelope)
|
u8::from(self.envelope)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -478,30 +463,25 @@ impl Channel1 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0xFF14 | NR14 - Channel 1 Frequency high
|
/// 0xFF14 | NR14 - Channel 1 Frequency high
|
||||||
pub(crate) fn set_freq_hi(&mut self, fs: &FrameSequencer, byte: u8) {
|
pub(crate) fn set_freq_hi(&mut self, byte: u8) {
|
||||||
let mut new_freq: FrequencyHigh = byte.into();
|
self.freq_hi = byte.into();
|
||||||
|
|
||||||
// If this bit is set, a trigger event occurs
|
// If this bit is set, a trigger event occurs
|
||||||
if new_freq.trigger() {
|
if self.freq_hi.initial() {
|
||||||
self.enabled = true;
|
if self.is_dac_enabled() {
|
||||||
|
self.enabled = true;
|
||||||
|
}
|
||||||
|
|
||||||
if self.length_counter == 0 {
|
// Length behaviour during trigger event
|
||||||
self.length_counter = 64;
|
if self.length_timer == 0 {
|
||||||
|
self.length_timer = 64;
|
||||||
|
}
|
||||||
|
|
||||||
// length_counter was 0 so length evidently wasn't enabled
|
// Envelope Behaviour during trigger event
|
||||||
self.freq_hi.set_length_enable(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
// reload freq_timer but last two bits are unmodified
|
|
||||||
self.freq_timer = obscure::square::freq_timer_reload(self.freq_timer, self.frequency());
|
|
||||||
|
|
||||||
// Volume Envelope loaded w/ period
|
|
||||||
self.period_timer = self.envelope.period();
|
self.period_timer = self.envelope.period();
|
||||||
|
|
||||||
// Channel Volume reloaded
|
|
||||||
self.current_volume = self.envelope.init_vol();
|
self.current_volume = self.envelope.init_vol();
|
||||||
|
|
||||||
// Channel 1 Sweep Behaviour
|
// Sweep behaviour during trigger event
|
||||||
let sweep_period = self.sweep.period();
|
let sweep_period = self.sweep.period();
|
||||||
let sweep_shift = self.sweep.shift_count();
|
let sweep_shift = self.sweep.shift_count();
|
||||||
|
|
||||||
|
@ -513,19 +493,7 @@ impl Channel1 {
|
||||||
if sweep_shift != 0 {
|
if sweep_shift != 0 {
|
||||||
let _ = self.calc_sweep_freq();
|
let _ = self.calc_sweep_freq();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.enabled = self.is_dac_enabled();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
obscure::nrx4::length_update(
|
|
||||||
&mut new_freq,
|
|
||||||
fs,
|
|
||||||
&mut self.length_counter,
|
|
||||||
&mut self.enabled,
|
|
||||||
self.freq_hi.length_enable(),
|
|
||||||
);
|
|
||||||
|
|
||||||
self.freq_hi = new_freq;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn tick(&mut self) {
|
fn tick(&mut self) {
|
||||||
|
@ -587,7 +555,7 @@ impl Channel1 {
|
||||||
pub(crate) struct Channel2 {
|
pub(crate) struct Channel2 {
|
||||||
/// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty
|
/// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty
|
||||||
duty: SoundDuty,
|
duty: SoundDuty,
|
||||||
/// 0xFF17 | NR22 - Channel 2 Volume Envelope
|
/// 0xFF17 | NR22 - Channel 2 Volume ENvelope
|
||||||
envelope: VolumeEnvelope,
|
envelope: VolumeEnvelope,
|
||||||
/// 0xFF18 | NR23 - Channel 2 Frequency low (lower 8 bits only)
|
/// 0xFF18 | NR23 - Channel 2 Frequency low (lower 8 bits only)
|
||||||
freq_lo: u8,
|
freq_lo: u8,
|
||||||
|
@ -599,7 +567,7 @@ pub(crate) struct Channel2 {
|
||||||
current_volume: u8,
|
current_volume: u8,
|
||||||
|
|
||||||
// Length Functionality
|
// Length Functionality
|
||||||
length_counter: u16,
|
length_timer: u16,
|
||||||
|
|
||||||
freq_timer: u16,
|
freq_timer: u16,
|
||||||
duty_pos: u8,
|
duty_pos: u8,
|
||||||
|
@ -616,15 +584,15 @@ impl Channel2 {
|
||||||
/// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty
|
/// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty
|
||||||
pub(crate) fn set_duty(&mut self, byte: u8) {
|
pub(crate) fn set_duty(&mut self, byte: u8) {
|
||||||
self.duty = byte.into();
|
self.duty = byte.into();
|
||||||
self.length_counter = 64 - self.duty.sound_length() as u16;
|
self.length_timer = 64 - self.duty.sound_length() as u16;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0xFF17 | NR22 - Channel 2 Volume Envelope
|
/// 0xFF17 | NR22 - Channel 2 Volume ENvelope
|
||||||
pub(crate) fn envelope(&self) -> u8 {
|
pub(crate) fn envelope(&self) -> u8 {
|
||||||
u8::from(self.envelope)
|
u8::from(self.envelope)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 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();
|
||||||
|
|
||||||
|
@ -644,36 +612,22 @@ impl Channel2 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0xFF19 | NR24 - Channel 2 Frequency high
|
/// 0xFF19 | NR24 - Channel 2 Frequency high
|
||||||
pub(crate) fn set_freq_hi(&mut self, fs: &FrameSequencer, byte: u8) {
|
pub(crate) fn set_freq_hi(&mut self, byte: u8) {
|
||||||
let prev_le = self.freq_hi.length_enable();
|
|
||||||
self.freq_hi = byte.into();
|
self.freq_hi = byte.into();
|
||||||
|
|
||||||
obscure::nrx4::length_update(
|
if self.freq_hi.initial() {
|
||||||
&mut self.freq_hi,
|
// Envelope behaviour during trigger event
|
||||||
fs,
|
|
||||||
&mut self.length_counter,
|
|
||||||
&mut self.enabled,
|
|
||||||
prev_le,
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.freq_hi.trigger() {
|
|
||||||
self.enabled = true;
|
|
||||||
|
|
||||||
// Reload length counter if need be
|
|
||||||
if self.length_counter == 0 {
|
|
||||||
self.length_counter = 64;
|
|
||||||
}
|
|
||||||
|
|
||||||
// reload frequency timer
|
|
||||||
self.freq_timer = obscure::square::freq_timer_reload(self.freq_timer, self.frequency());
|
|
||||||
|
|
||||||
// reload envelope
|
|
||||||
self.period_timer = self.envelope.period();
|
self.period_timer = self.envelope.period();
|
||||||
|
|
||||||
// reload volume
|
|
||||||
self.current_volume = self.envelope.init_vol();
|
self.current_volume = self.envelope.init_vol();
|
||||||
|
|
||||||
self.enabled = self.is_dac_enabled();
|
// Length behaviour during trigger event
|
||||||
|
if self.length_timer == 0 {
|
||||||
|
self.length_timer = 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.is_dac_enabled() {
|
||||||
|
self.enabled = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -724,7 +678,7 @@ pub(crate) struct Channel3 {
|
||||||
wave_ram: [u8; WAVE_PATTERN_RAM_LEN],
|
wave_ram: [u8; WAVE_PATTERN_RAM_LEN],
|
||||||
|
|
||||||
// Length Functionality
|
// Length Functionality
|
||||||
length_counter: u16,
|
length_timer: u16,
|
||||||
|
|
||||||
freq_timer: u16,
|
freq_timer: u16,
|
||||||
offset: u8,
|
offset: u8,
|
||||||
|
@ -770,7 +724,7 @@ impl Channel3 {
|
||||||
/// 0xFF1B | NR31 - Sound Length
|
/// 0xFF1B | NR31 - Sound Length
|
||||||
pub(crate) fn set_len(&mut self, byte: u8) {
|
pub(crate) fn set_len(&mut self, byte: u8) {
|
||||||
self.len = byte;
|
self.len = byte;
|
||||||
self.length_counter = 256 - self.len as u16;
|
self.length_timer = 256 - self.len as u16;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0xFF1C | NR32 - Channel 3 Volume
|
/// 0xFF1C | NR32 - Channel 3 Volume
|
||||||
|
@ -802,32 +756,18 @@ impl Channel3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0xFF1E | NR34 - Channel 3 Frequency high
|
/// 0xFF1E | NR34 - Channel 3 Frequency high
|
||||||
pub(crate) fn set_freq_hi(&mut self, fs: &FrameSequencer, byte: u8) {
|
pub(crate) fn set_freq_hi(&mut self, byte: u8) {
|
||||||
let prev_le = self.freq_hi.length_enable();
|
|
||||||
self.freq_hi = byte.into();
|
self.freq_hi = byte.into();
|
||||||
|
|
||||||
obscure::nrx4::length_update(
|
if self.freq_hi.initial() {
|
||||||
&mut self.freq_hi,
|
|
||||||
fs,
|
|
||||||
&mut self.length_counter,
|
|
||||||
&mut self.enabled,
|
|
||||||
prev_le,
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.freq_hi.trigger() {
|
|
||||||
self.enabled = true;
|
|
||||||
|
|
||||||
// Length behaviour during trigger event
|
// Length behaviour during trigger event
|
||||||
if self.length_counter == 0 {
|
if self.length_timer == 0 {
|
||||||
self.length_counter = 256;
|
self.length_timer = 256;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.freq_timer = (2048 - self.frequency()) * 2;
|
if self.dac_enabled {
|
||||||
|
self.enabled = true;
|
||||||
// reset wave channel's ptr into wave RAM
|
}
|
||||||
self.offset = 0;
|
|
||||||
|
|
||||||
self.enabled = self.dac_enabled;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -886,7 +826,7 @@ pub(crate) struct Channel4 {
|
||||||
current_volume: u8,
|
current_volume: u8,
|
||||||
|
|
||||||
// Length Functionality
|
// Length Functionality
|
||||||
length_counter: u16,
|
length_timer: u16,
|
||||||
|
|
||||||
/// Linear Feedback Shift Register (15-bit)
|
/// Linear Feedback Shift Register (15-bit)
|
||||||
lf_shift: u16,
|
lf_shift: u16,
|
||||||
|
@ -900,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_counter = 64 - self.len as u16;
|
self.length_timer = 64 - self.len as u16;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0xFF21 | NR42 - Channel 4 Volume Envelope
|
/// 0xFF21 | NR42 - Channel 4 Volume Envelope
|
||||||
|
@ -933,37 +873,25 @@ impl Channel4 {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0xFF23 | NR44 - Channel 4 Counter / Consecutive Selector and Restart
|
/// 0xFF23 | NR44 - Channel 4 Counter / Consecutive Selector and Restart
|
||||||
pub(crate) fn set_frequency(&mut self, fs: &FrameSequencer, byte: u8) {
|
pub(crate) fn set_frequency(&mut self, byte: u8) {
|
||||||
let prev_le = self.freq.length_enable();
|
|
||||||
self.freq = byte.into();
|
self.freq = byte.into();
|
||||||
|
|
||||||
obscure::nrx4::length_update(
|
if self.freq.initial() {
|
||||||
&mut self.freq,
|
// Envelope behaviour during trigger event
|
||||||
fs,
|
|
||||||
&mut self.length_counter,
|
|
||||||
&mut self.enabled,
|
|
||||||
prev_le,
|
|
||||||
);
|
|
||||||
|
|
||||||
if self.freq.trigger() {
|
|
||||||
self.enabled = true;
|
|
||||||
|
|
||||||
if self.length_counter == 0 {
|
|
||||||
self.length_counter = 64;
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME: Frequency Timer reloaded?
|
|
||||||
|
|
||||||
// reload envelope
|
|
||||||
self.period_timer = self.envelope.period();
|
self.period_timer = self.envelope.period();
|
||||||
|
|
||||||
//reload volume
|
|
||||||
self.current_volume = self.envelope.init_vol();
|
self.current_volume = self.envelope.init_vol();
|
||||||
|
|
||||||
// LFSR reset
|
// Length behaviour during trigger event
|
||||||
|
if self.length_timer == 0 {
|
||||||
|
self.length_timer = 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
// LFSR behaviour during trigger event
|
||||||
self.lf_shift = 0x7FFF;
|
self.lf_shift = 0x7FFF;
|
||||||
|
|
||||||
self.enabled = self.is_dac_enabled();
|
if self.is_dac_enabled() {
|
||||||
|
self.enabled = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1008,39 +936,3 @@ impl Channel4 {
|
||||||
code << 4
|
code << 4
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
mod obscure {
|
|
||||||
pub(super) mod square {
|
|
||||||
pub(crate) fn freq_timer_reload(freq_timer: u16, frequency: u16) -> u16 {
|
|
||||||
(freq_timer & 0x0003) | (((2048 - frequency) * 4) & 0xFFFC)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) mod nrx4 {
|
|
||||||
use super::super::{FrameSequencer, NRx4};
|
|
||||||
|
|
||||||
/// Implements the obscure behaviour when writing to NRX4 under certain
|
|
||||||
/// conditions
|
|
||||||
///
|
|
||||||
/// # Arguments
|
|
||||||
/// * `freq_hi` - mutable reference to a channel's frequency high register
|
|
||||||
/// * `fs` - reference to the APU's frame sequencer
|
|
||||||
/// * `counter` - mutable reference to a channel's internal enabled flag
|
|
||||||
/// * `prev_le` - what length_enable was before NRx4 was written with a new value
|
|
||||||
pub(crate) fn length_update(
|
|
||||||
freq: &mut impl NRx4,
|
|
||||||
fs: &FrameSequencer,
|
|
||||||
counter: &mut u16,
|
|
||||||
enabled: &mut bool,
|
|
||||||
prev_le: bool,
|
|
||||||
) {
|
|
||||||
if !fs.next_clocks_length() && !prev_le && freq.length_enable() && *counter != 0 {
|
|
||||||
*counter -= 1;
|
|
||||||
|
|
||||||
if *counter == 0 && !freq.trigger() {
|
|
||||||
*enabled = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -5,12 +5,33 @@ pub(crate) const SAMPLE_RATE: u32 = 48000; // Hz
|
||||||
const CHANNEL_COUNT: usize = 2;
|
const CHANNEL_COUNT: usize = 2;
|
||||||
const BUFFER_CAPACITY: usize = 2048 * CHANNEL_COUNT; // # of samples * the # of channels
|
const BUFFER_CAPACITY: usize = 2048 * CHANNEL_COUNT; // # of samples * the # of channels
|
||||||
|
|
||||||
pub fn init<T>() -> (SampleProducer<T>, SampleConsumer<T>) {
|
pub struct AudioSPSC<T> {
|
||||||
let (prod, cons) = RingBuffer::new(BUFFER_CAPACITY);
|
inner: RingBuffer<T>,
|
||||||
(
|
}
|
||||||
SampleProducer { inner: prod },
|
|
||||||
SampleConsumer { inner: cons },
|
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> {
|
pub struct SampleProducer<T> {
|
||||||
|
|
|
@ -1,11 +1,5 @@
|
||||||
use bitfield::bitfield;
|
use bitfield::bitfield;
|
||||||
|
|
||||||
pub(crate) trait NRx4 {
|
|
||||||
fn trigger(&self) -> bool;
|
|
||||||
fn length_enable(&self) -> bool;
|
|
||||||
fn set_length_enable(&mut self, value: bool);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) mod ch1 {
|
pub(crate) mod ch1 {
|
||||||
use super::bitfield;
|
use super::bitfield;
|
||||||
|
|
||||||
|
@ -109,7 +103,7 @@ pub(super) mod ch3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) mod ch4 {
|
pub(super) mod ch4 {
|
||||||
use super::{bitfield, NRx4};
|
use super::bitfield;
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
pub struct PolynomialCounter(u8);
|
pub struct PolynomialCounter(u8);
|
||||||
|
@ -169,7 +163,7 @@ pub(super) mod ch4 {
|
||||||
match byte & 0x01 {
|
match byte & 0x01 {
|
||||||
0b00 => Self::Short,
|
0b00 => Self::Short,
|
||||||
0b01 => Self::Long,
|
0b01 => Self::Long,
|
||||||
_ => unreachable!("{:#04X} is not a valid value for CounterWidth", byte),
|
_ => unreachable!("{:#04X} is not a valid value for CounterWidth"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -183,21 +177,17 @@ pub(super) mod ch4 {
|
||||||
bitfield! {
|
bitfield! {
|
||||||
pub struct Frequency(u8);
|
pub struct Frequency(u8);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
_trigger, _: 7;
|
_initial, _: 7;
|
||||||
_length_enable, _set_length_enable: 6;
|
_length_disable, _: 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NRx4 for Frequency {
|
impl Frequency {
|
||||||
fn trigger(&self) -> bool {
|
pub(crate) fn length_disable(&self) -> bool {
|
||||||
self._trigger()
|
self._length_disable()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn length_enable(&self) -> bool {
|
pub(crate) fn initial(&self) -> bool {
|
||||||
self._length_enable()
|
self._initial()
|
||||||
}
|
|
||||||
|
|
||||||
fn set_length_enable(&mut self, value: bool) {
|
|
||||||
self._set_length_enable(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,27 +218,23 @@ pub(super) mod ch4 {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) mod common {
|
pub(super) mod common {
|
||||||
use super::{bitfield, NRx4};
|
use super::bitfield;
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
pub struct FrequencyHigh(u8);
|
pub struct FrequencyHigh(u8);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
_trigger, _: 7;
|
_initial, _: 7;
|
||||||
_length_enable, _set_length_enable: 6;
|
_length_disable, _: 6;
|
||||||
pub freq_bits, set_freq_bits: 2, 0;
|
pub freq_bits, set_freq_bits: 2, 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
impl NRx4 for FrequencyHigh {
|
impl FrequencyHigh {
|
||||||
fn trigger(&self) -> bool {
|
pub(crate) fn initial(&self) -> bool {
|
||||||
self._trigger()
|
self._initial()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn length_enable(&self) -> bool {
|
pub(crate) fn length_disable(&self) -> bool {
|
||||||
self._length_enable()
|
self._length_disable()
|
||||||
}
|
|
||||||
|
|
||||||
fn set_length_enable(&mut self, value: bool) {
|
|
||||||
self._set_length_enable(value);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -416,6 +402,12 @@ pub(super) mod common {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for WavePattern {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::OneEighth // Rationale: OneEighth is 0x00
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<WavePattern> for u8 {
|
impl From<WavePattern> for u8 {
|
||||||
fn from(pattern: WavePattern) -> Self {
|
fn from(pattern: WavePattern) -> Self {
|
||||||
pattern as Self
|
pattern as Self
|
||||||
|
@ -473,6 +465,12 @@ impl Clone for SoundOutput {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for SoundOutput {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for SoundOutput {
|
impl From<u8> for SoundOutput {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
@ -511,6 +509,12 @@ impl Clone for ChannelControl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ChannelControl {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for ChannelControl {
|
impl From<u8> for ChannelControl {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
@ -553,15 +557,6 @@ pub(super) mod fs {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn next_clocks_length(&self) -> bool {
|
|
||||||
use State::*;
|
|
||||||
|
|
||||||
match self.state {
|
|
||||||
Length | LengthAndSweep => true,
|
|
||||||
Nothing | Envelope => false,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn state(&self) -> State {
|
pub(crate) fn state(&self) -> State {
|
||||||
self.state
|
self.state
|
||||||
}
|
}
|
||||||
|
@ -572,7 +567,7 @@ pub(super) mod fs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub(crate) enum State {
|
pub(crate) enum State {
|
||||||
Length,
|
Length,
|
||||||
Nothing,
|
Nothing,
|
||||||
|
|
77
src/bus.rs
77
src/bus.rs
|
@ -10,19 +10,37 @@ use crate::work_ram::{VariableWorkRam, WorkRam};
|
||||||
|
|
||||||
pub(crate) const BOOT_SIZE: usize = 0x100;
|
pub(crate) const BOOT_SIZE: usize = 0x100;
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug)]
|
||||||
pub struct Bus {
|
pub struct Bus {
|
||||||
boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long
|
boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long
|
||||||
pub(crate) cart: 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,
|
||||||
timer: Timer,
|
pub(crate) timer: Timer,
|
||||||
int: Interrupt,
|
int: Interrupt,
|
||||||
pub(crate) apu: Apu,
|
pub(crate) apu: Apu,
|
||||||
high_ram: HighRam,
|
high_ram: HighRam,
|
||||||
serial: Serial,
|
serial: Serial,
|
||||||
pub(crate) joyp: Joypad,
|
pub(crate) joypad: Joypad,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Bus {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
boot: None,
|
||||||
|
cart: None,
|
||||||
|
ppu: Default::default(),
|
||||||
|
work_ram: Default::default(),
|
||||||
|
var_ram: Default::default(),
|
||||||
|
timer: Default::default(),
|
||||||
|
int: Default::default(),
|
||||||
|
apu: Default::default(),
|
||||||
|
high_ram: Default::default(),
|
||||||
|
serial: Default::default(),
|
||||||
|
joypad: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bus {
|
impl Bus {
|
||||||
|
@ -33,24 +51,26 @@ impl Bus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn load_cart(&mut self, rom: Vec<u8>) {
|
||||||
|
self.cart = Some(Cartridge::new(rom));
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn cart_title(&self) -> Option<&str> {
|
||||||
|
self.cart.as_ref()?.title()
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
pub(crate) fn boot_mapped(&self) -> bool {
|
pub(crate) fn boot_mapped(&self) -> bool {
|
||||||
self.boot.is_some()
|
self.boot.is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn clock(&mut self) {
|
pub(crate) fn clock(&mut self) {
|
||||||
self.tick(4);
|
self.tick(4);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn tick(&mut self, limit: u8) {
|
fn tick(&mut self, limit: u8) {
|
||||||
for _ in 0..limit {
|
for _ in 0..limit {
|
||||||
self.timer.tick();
|
self.timer.tick();
|
||||||
|
|
||||||
if let Some(c) = self.cart.as_mut() {
|
|
||||||
c.tick()
|
|
||||||
}
|
|
||||||
self.ppu.tick();
|
self.ppu.tick();
|
||||||
self.apu.tick(self.timer.divider);
|
self.apu.tick(self.timer.divider);
|
||||||
self.dma_tick()
|
self.dma_tick()
|
||||||
|
@ -66,7 +86,7 @@ impl Bus {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Bus {
|
impl Bus {
|
||||||
pub(crate) fn oam_read_byte(&self, addr: u16) -> u8 {
|
pub fn oam_read_byte(&self, addr: u16) -> u8 {
|
||||||
match addr {
|
match addr {
|
||||||
0x0000..=0x7FFF => {
|
0x0000..=0x7FFF => {
|
||||||
// 16KB ROM bank 00 (ends at 0x3FFF)
|
// 16KB ROM bank 00 (ends at 0x3FFF)
|
||||||
|
@ -79,14 +99,14 @@ impl Bus {
|
||||||
|
|
||||||
match self.cart.as_ref() {
|
match self.cart.as_ref() {
|
||||||
Some(cart) => cart.read_byte(addr),
|
Some(cart) => cart.read_byte(addr),
|
||||||
None => 0xFF,
|
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.cart.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 => 0xFF,
|
None => panic!("Tried to read from a non-existent cartridge"),
|
||||||
},
|
},
|
||||||
0xC000..=0xCFFF => self.work_ram.read_byte(addr), // 4KB Work RAM Bank 0
|
0xC000..=0xCFFF => self.work_ram.read_byte(addr), // 4KB Work RAM Bank 0
|
||||||
0xD000..=0xDFFF => self.var_ram.read_byte(addr), // 4KB Work RAM Bank 1 -> N
|
0xD000..=0xDFFF => self.var_ram.read_byte(addr), // 4KB Work RAM Bank 1 -> N
|
||||||
|
@ -113,7 +133,7 @@ impl Bus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn oam_write_byte(&mut self, addr: u16, byte: u8) {
|
pub fn oam_write_byte(&mut self, addr: u16, byte: u8) {
|
||||||
self.ppu.oam.write_byte(addr, byte);
|
self.ppu.oam.write_byte(addr, byte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,7 +152,7 @@ impl BusIo for Bus {
|
||||||
|
|
||||||
match self.cart.as_ref() {
|
match self.cart.as_ref() {
|
||||||
Some(cart) => cart.read_byte(addr),
|
Some(cart) => cart.read_byte(addr),
|
||||||
None => 0xFF,
|
None => panic!("Tried to read from a non-existent cartridge"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0x8000..=0x9FFF => {
|
0x8000..=0x9FFF => {
|
||||||
|
@ -145,7 +165,7 @@ impl BusIo for Bus {
|
||||||
0xA000..=0xBFFF => match self.cart.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 => 0xFF,
|
None => panic!("Tried to read from a non-existent cartridge"),
|
||||||
},
|
},
|
||||||
0xC000..=0xCFFF => self.work_ram.read_byte(addr), // 4KB Work RAM Bank 0
|
0xC000..=0xCFFF => self.work_ram.read_byte(addr), // 4KB Work RAM Bank 0
|
||||||
0xD000..=0xDFFF => self.var_ram.read_byte(addr), // 4KB Work RAM Bank 1 -> N
|
0xD000..=0xDFFF => self.var_ram.read_byte(addr), // 4KB Work RAM Bank 1 -> N
|
||||||
|
@ -192,7 +212,7 @@ impl BusIo for Bus {
|
||||||
// Every address here starts with 0xFF so we can just check the
|
// Every address here starts with 0xFF so we can just check the
|
||||||
// low byte to figure out which register it is
|
// low byte to figure out which register it is
|
||||||
match addr & 0x00FF {
|
match addr & 0x00FF {
|
||||||
0x00 => self.joyp.p1,
|
0x00 => self.joypad.p1,
|
||||||
0x01 => self.serial.next,
|
0x01 => self.serial.next,
|
||||||
0x02 => self.serial.ctrl.into(),
|
0x02 => self.serial.ctrl.into(),
|
||||||
0x04 => (self.timer.divider >> 8) as u8,
|
0x04 => (self.timer.divider >> 8) as u8,
|
||||||
|
@ -213,9 +233,8 @@ impl BusIo for Bus {
|
||||||
0x49 => self.ppu.monochrome.obj_palette_1.into(),
|
0x49 => self.ppu.monochrome.obj_palette_1.into(),
|
||||||
0x4A => self.ppu.pos.window_y,
|
0x4A => self.ppu.pos.window_y,
|
||||||
0x4B => self.ppu.pos.window_x,
|
0x4B => self.ppu.pos.window_x,
|
||||||
0x4F => 0xFF, // CGB VRAM Bank Select
|
|
||||||
_ => {
|
_ => {
|
||||||
tracing::warn!("Attempted read from {:#06X} on IO", addr);
|
eprintln!("Read 0xFF from unused IO register {:#06X}.", addr);
|
||||||
0xFF
|
0xFF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -236,8 +255,9 @@ 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)
|
||||||
if let Some(cart) = self.cart.as_mut() {
|
match self.cart.as_mut() {
|
||||||
cart.write_byte(addr, byte);
|
Some(cart) => cart.write_byte(addr, byte),
|
||||||
|
None => panic!("Tried to write into non-existent cartridge"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0x8000..=0x9FFF => {
|
0x8000..=0x9FFF => {
|
||||||
|
@ -249,8 +269,9 @@ impl BusIo for Bus {
|
||||||
}
|
}
|
||||||
0xA000..=0xBFFF => {
|
0xA000..=0xBFFF => {
|
||||||
// 8KB External RAM
|
// 8KB External RAM
|
||||||
if let Some(cart) = self.cart.as_mut() {
|
match self.cart.as_mut() {
|
||||||
cart.write_byte(addr, byte);
|
Some(cart) => cart.write_byte(addr, byte),
|
||||||
|
None => panic!("Tried to write into non-existent cartridge"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0xC000..=0xCFFF => self.work_ram.write_byte(addr, byte), // 4KB Work RAM Bank 0
|
0xC000..=0xCFFF => self.work_ram.write_byte(addr, byte), // 4KB Work RAM Bank 0
|
||||||
|
@ -290,7 +311,7 @@ impl BusIo for Bus {
|
||||||
// Every address here starts with 0xFF so we can just check the
|
// Every address here starts with 0xFF so we can just check the
|
||||||
// low byte to figure out which register it is
|
// low byte to figure out which register it is
|
||||||
match addr & 0x00FF {
|
match addr & 0x00FF {
|
||||||
0x00 => self.joyp.update(byte),
|
0x00 => self.joypad.update(byte),
|
||||||
0x01 => self.serial.next = byte,
|
0x01 => self.serial.next = byte,
|
||||||
0x02 => self.serial.ctrl = byte.into(),
|
0x02 => self.serial.ctrl = byte.into(),
|
||||||
0x04 => self.timer.divider = 0x0000,
|
0x04 => self.timer.divider = 0x0000,
|
||||||
|
@ -324,15 +345,13 @@ impl BusIo for Bus {
|
||||||
0x4A => self.ppu.pos.window_y = byte,
|
0x4A => self.ppu.pos.window_y = byte,
|
||||||
0x4B => self.ppu.pos.window_x = byte,
|
0x4B => self.ppu.pos.window_x = byte,
|
||||||
0x4D => {} // CGB Specific Register
|
0x4D => {} // CGB Specific Register
|
||||||
0x4F => {} // CGB VRAM Bank Select
|
|
||||||
0x50 => {
|
0x50 => {
|
||||||
// Disable Boot ROM
|
// Disable Boot ROM
|
||||||
if byte != 0 {
|
if byte != 0 {
|
||||||
self.boot = None;
|
self.boot = None;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
0x70 => {} // CGB WRAM Bank Select
|
_ => eprintln!("Wrote {:#04X} to unused IO register {:#06X}.", byte, addr),
|
||||||
_ => tracing::warn!("Attempted write of {:#04X} to {:#06X} on IO", byte, addr),
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
0xFF80..=0xFFFE => {
|
0xFF80..=0xFFFE => {
|
||||||
|
@ -354,7 +373,7 @@ impl Bus {
|
||||||
let lcd_stat = self.ppu.int.lcd_stat();
|
let lcd_stat = self.ppu.int.lcd_stat();
|
||||||
|
|
||||||
// Read the current interrupt information from the Joypad
|
// Read the current interrupt information from the Joypad
|
||||||
let joypad = self.joyp.interrupt();
|
let joypad = self.joypad.interrupt();
|
||||||
|
|
||||||
// Read the current interrupt information from the Timer
|
// Read the current interrupt information from the Timer
|
||||||
let timer = self.timer.interrupt();
|
let timer = self.timer.interrupt();
|
||||||
|
@ -384,7 +403,7 @@ impl Bus {
|
||||||
self.ppu.int.set_lcd_stat(lcd_stat);
|
self.ppu.int.set_lcd_stat(lcd_stat);
|
||||||
|
|
||||||
// Update the Joypad's instance of the following interrupts
|
// Update the Joypad's instance of the following interrupts
|
||||||
self.joyp.set_interrupt(joypad);
|
self.joypad.set_interrupt(joypad);
|
||||||
|
|
||||||
// Update the Timer's instance of the following interrupts
|
// Update the Timer's instance of the following interrupts
|
||||||
self.timer.set_interrupt(timer);
|
self.timer.set_interrupt(timer);
|
||||||
|
|
713
src/cartridge.rs
713
src/cartridge.rs
|
@ -1,131 +1,103 @@
|
||||||
use bitfield::bitfield;
|
|
||||||
|
|
||||||
use crate::bus::BusIo;
|
use crate::bus::BusIo;
|
||||||
use crate::emu::SM83_CLOCK_SPEED;
|
|
||||||
use crate::Cycle;
|
|
||||||
|
|
||||||
const RAM_SIZE_ADDRESS: usize = 0x0149;
|
const RAM_SIZE_ADDRESS: usize = 0x0149;
|
||||||
const ROM_SIZE_ADDRESS: usize = 0x0148;
|
const ROM_SIZE_ADDRESS: usize = 0x0148;
|
||||||
const MBC_KIND_ADDRESS: usize = 0x0147;
|
const MBC_TYPE_ADDRESS: usize = 0x0147;
|
||||||
const ROM_TITLE_START: usize = 0x134;
|
const ROM_TITLE_RANGE: std::ops::RangeInclusive<usize> = 0x0134..=0x0143;
|
||||||
const ROM_TITLE_MAX_SIZE: usize = 16;
|
|
||||||
const ROM_MANUFACTURER_START: usize = 0x13F;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct Cartridge {
|
pub(crate) struct Cartridge {
|
||||||
mem: Vec<u8>,
|
memory: Vec<u8>,
|
||||||
pub(crate) title: Option<String>,
|
title: Option<String>,
|
||||||
mbc: Box<dyn MbcIo>,
|
mbc: Box<dyn MBCIo>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cartridge {
|
impl Cartridge {
|
||||||
pub(crate) fn new(mem: Vec<u8>) -> Self {
|
pub(crate) fn new(memory: Vec<u8>) -> Self {
|
||||||
let title_mem: &[u8; 16] = mem[ROM_TITLE_START..(ROM_TITLE_START + ROM_TITLE_MAX_SIZE)]
|
let title = Self::find_title(&memory);
|
||||||
.try_into()
|
eprintln!("Cartridge Title: {:?}", title);
|
||||||
.expect("coerce slice containing cartridge title from ROM to [u8; 16]");
|
|
||||||
|
|
||||||
let title = Self::detect_title(title_mem);
|
Self {
|
||||||
let mbc = Self::detect_mbc(&mem);
|
mbc: Self::detect_mbc(&memory),
|
||||||
tracing::info!("Title: {:?}", title);
|
title,
|
||||||
|
memory,
|
||||||
Self { mem, title, mbc }
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn ext_ram(&self) -> Option<&[u8]> {
|
fn detect_mbc(memory: &[u8]) -> Box<dyn MBCIo> {
|
||||||
self.mbc.ext_ram()
|
let ram_size = Self::detect_ram_info(memory);
|
||||||
}
|
let rom_size = Self::detect_rom_info(memory);
|
||||||
|
let mbc_kind = Self::find_mbc(memory);
|
||||||
pub(crate) fn ext_ram_mut(&mut self) -> Option<&mut [u8]> {
|
|
||||||
self.mbc.ext_ram_mut()
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub(crate) fn tick(&mut self) {
|
|
||||||
self.mbc.tick()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn detect_mbc(mem: &[u8]) -> Box<dyn MbcIo> {
|
|
||||||
let ram_size: RamSize = mem[RAM_SIZE_ADDRESS].into();
|
|
||||||
let rom_size: RomSize = mem[ROM_SIZE_ADDRESS].into();
|
|
||||||
let mbc_kind = Self::detect_mbc_kind(mem[MBC_KIND_ADDRESS]);
|
|
||||||
let ram_cap = ram_size.capacity();
|
let ram_cap = ram_size.capacity();
|
||||||
let rom_cap = rom_size.capacity();
|
let rom_cap = rom_size.capacity();
|
||||||
|
|
||||||
tracing::info!("RAM size: {} bytes", ram_cap);
|
eprintln!("Cartridge Ram Size: {} bytes", ram_cap);
|
||||||
tracing::info!("ROM size: {} bytes", rom_cap);
|
eprintln!("Cartridge ROM Size: {} bytes", rom_size.capacity());
|
||||||
tracing::info!("MBC kind: {:?}", 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(hw) => Box::new(Mbc1::new(hw, ram_size, rom_size)),
|
MBCKind::MBC1 => Box::new(MBC1::new(ram_size, rom_size)),
|
||||||
MbcKind::Mbc2(hw) => Box::new(Mbc2::new(hw, rom_cap)),
|
MBCKind::MBC1WithBattery => Box::new(MBC1::new(ram_size, rom_size)), // TODO: Implement Saving
|
||||||
MbcKind::Mbc3(hw @ Mbc3Hardware::Rtc) => Box::new(Mbc3::new(hw, ram_cap)),
|
MBCKind::MBC2 => Box::new(MBC2::new(rom_cap)),
|
||||||
MbcKind::Mbc3(hw @ Mbc3Hardware::RtcBatteryRam) => Box::new(Mbc3::new(hw, ram_cap)),
|
MBCKind::MBC2WithBattery => Box::new(MBC2::new(rom_cap)), // TODO: Implement Saving
|
||||||
MbcKind::Mbc3(hw @ Mbc3Hardware::BatteryRam) => Box::new(Mbc3::new(hw, ram_cap)),
|
MBCKind::MBC3 => Box::new(MBC3::new(ram_cap)),
|
||||||
MbcKind::Mbc5(hw @ Mbc5Hardware::None) => Box::new(Mbc5::new(hw, ram_cap, rom_cap)),
|
MBCKind::MBC3WithBattery => Box::new(MBC3::new(ram_cap)), // TODO: Implement Saving
|
||||||
MbcKind::Mbc5(hw @ Mbc5Hardware::BatteryRam) => {
|
MBCKind::MBC5 => Box::new(MBC5::new(ram_cap, rom_cap)),
|
||||||
Box::new(Mbc5::new(hw, ram_cap, rom_cap))
|
MBCKind::MBC5WithBattery => Box::new(MBC5::new(ram_cap, rom_cap)), // TDO: Implement Saving
|
||||||
}
|
|
||||||
kind => todo!("ROMS with {:?} are currently unsupported", kind),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn detect_title(title_mem: &[u8; ROM_TITLE_MAX_SIZE]) -> Option<String> {
|
fn find_title(memory: &[u8]) -> Option<String> {
|
||||||
use std::str::from_utf8;
|
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());
|
||||||
|
|
||||||
const ALT_TITLE_LEN: usize = ROM_MANUFACTURER_START - ROM_TITLE_START;
|
match trimmed {
|
||||||
|
Some("") | None => None,
|
||||||
// ascii byte slie does not have a null-terminator
|
Some(_) => trimmed.map(String::from),
|
||||||
let ascii = match title_mem.iter().position(|b| *b == 0x00) {
|
|
||||||
Some(end) => &title_mem[..end],
|
|
||||||
None => &title_mem[..ROM_TITLE_MAX_SIZE],
|
|
||||||
};
|
|
||||||
|
|
||||||
match from_utf8(ascii).map(str::trim).ok() {
|
|
||||||
None => match from_utf8(&title_mem[..ALT_TITLE_LEN]).map(str::trim).ok() {
|
|
||||||
Some("") | None => None,
|
|
||||||
Some(title) => Some(String::from(title)),
|
|
||||||
},
|
|
||||||
Some("") => None,
|
|
||||||
Some(title) => Some(String::from(title)),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn detect_mbc_kind(id: u8) -> MbcKind {
|
pub(crate) fn title(&self) -> Option<&str> {
|
||||||
use MbcKind::*;
|
self.title.as_deref()
|
||||||
|
}
|
||||||
|
|
||||||
match id {
|
fn detect_ram_info(memory: &[u8]) -> RamSize {
|
||||||
|
let id = memory[RAM_SIZE_ADDRESS];
|
||||||
|
id.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn detect_rom_info(memory: &[u8]) -> RomSize {
|
||||||
|
let id = memory[ROM_SIZE_ADDRESS];
|
||||||
|
id.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_mbc(memory: &[u8]) -> MBCKind {
|
||||||
|
use MBCKind::*;
|
||||||
|
|
||||||
|
match memory[MBC_TYPE_ADDRESS] {
|
||||||
0x00 => None,
|
0x00 => None,
|
||||||
0x01 => Mbc1(Mbc1Hardware::None),
|
0x01 | 0x02 => MBC1,
|
||||||
0x02 => Mbc1(Mbc1Hardware::Ram),
|
0x03 => MBC1WithBattery,
|
||||||
0x03 => Mbc1(Mbc1Hardware::BatteryRam),
|
0x05 => MBC2,
|
||||||
0x05 => Mbc2(Mbc2Hardware::None),
|
0x06 => MBC2WithBattery,
|
||||||
0x06 => Mbc2(Mbc2Hardware::BatteryRam),
|
0x19 | 0x1A => MBC5,
|
||||||
0x08 | 0x09 => unimplemented!("NoMBC + RAM and NoMBC + Battery unsupported"),
|
0x1B => MBC5WithBattery,
|
||||||
0x0B | 0x0C | 0x0D => unimplemented!("MM01 unsupported"),
|
0x13 => MBC3WithBattery,
|
||||||
0x0F => Mbc3(Mbc3Hardware::Rtc),
|
0x11 | 0x12 => MBC3,
|
||||||
0x10 => Mbc3(Mbc3Hardware::RtcBatteryRam),
|
id => unimplemented!("id {:#04X} is an unsupported MBC", id),
|
||||||
0x11 => Mbc3(Mbc3Hardware::None),
|
|
||||||
0x12 => Mbc3(Mbc3Hardware::Ram),
|
|
||||||
0x13 => Mbc3(Mbc3Hardware::BatteryRam),
|
|
||||||
0x19 => Mbc5(Mbc5Hardware::None),
|
|
||||||
0x1A => Mbc5(Mbc5Hardware::Ram),
|
|
||||||
0x1B => Mbc5(Mbc5Hardware::BatteryRam),
|
|
||||||
0x1C => Mbc5(Mbc5Hardware::Rumble),
|
|
||||||
0x1D => Mbc5(Mbc5Hardware::RumbleRam),
|
|
||||||
0x1E => Mbc5(Mbc5Hardware::RumbleBatteryRam),
|
|
||||||
id => unimplemented!("MBC with code {:#04X} is unsupported", id),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BusIo for Cartridge {
|
impl BusIo for Cartridge {
|
||||||
fn read_byte(&self, addr: u16) -> u8 {
|
fn read_byte(&self, addr: u16) -> u8 {
|
||||||
use MbcResult::*;
|
use MBCResult::*;
|
||||||
|
|
||||||
match self.mbc.handle_read(addr) {
|
match self.mbc.handle_read(addr) {
|
||||||
Addr(addr) => self.mem[addr],
|
Address(addr) => self.memory[addr],
|
||||||
Byte(byte) => byte,
|
Value(byte) => byte,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,31 +107,28 @@ impl BusIo for Cartridge {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Mbc1 {
|
struct MBC1 {
|
||||||
/// 5-bit number
|
/// 5-bit number
|
||||||
rom_bank: u8,
|
rom_bank: u8,
|
||||||
/// 2-bit number
|
/// 2-bit number
|
||||||
ram_bank: u8,
|
ram_bank: u8,
|
||||||
mode: bool,
|
mode: bool,
|
||||||
ram_size: RamSize,
|
ram_size: RamSize,
|
||||||
mem: Vec<u8>,
|
memory: Vec<u8>,
|
||||||
rom_size: RomSize,
|
rom_size: RomSize,
|
||||||
mem_enabled: bool,
|
mem_enabled: bool,
|
||||||
|
|
||||||
hw: Mbc1Hardware,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mbc1 {
|
impl MBC1 {
|
||||||
fn new(hw: Mbc1Hardware, ram_size: RamSize, rom_size: RomSize) -> Self {
|
fn new(ram_size: RamSize, rom_size: RomSize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
rom_bank: 0x01,
|
rom_bank: 0x01,
|
||||||
mem: vec![0; ram_size.capacity() as usize],
|
memory: vec![0; ram_size.capacity() as usize],
|
||||||
ram_size,
|
ram_size,
|
||||||
rom_size,
|
rom_size,
|
||||||
ram_bank: Default::default(),
|
ram_bank: Default::default(),
|
||||||
mode: Default::default(),
|
mode: Default::default(),
|
||||||
mem_enabled: Default::default(),
|
mem_enabled: Default::default(),
|
||||||
hw,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -228,34 +197,22 @@ impl Mbc1 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Savable for Mbc1 {
|
impl MBCIo for MBC1 {
|
||||||
fn ext_ram(&self) -> Option<&[u8]> {
|
fn handle_read(&self, addr: u16) -> MBCResult {
|
||||||
match self.hw {
|
use MBCResult::*;
|
||||||
Mbc1Hardware::BatteryRam => Some(&self.mem),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ext_ram_mut(&mut self) -> Option<&mut [u8]> {
|
|
||||||
match self.hw {
|
|
||||||
Mbc1Hardware::BatteryRam => Some(&mut self.mem),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MbcIo for Mbc1 {
|
|
||||||
fn handle_read(&self, addr: u16) -> MbcResult {
|
|
||||||
use MbcResult::*;
|
|
||||||
|
|
||||||
match addr {
|
match addr {
|
||||||
0x0000..=0x3FFF if self.mode => {
|
0x0000..=0x3FFF if self.mode => {
|
||||||
Addr(0x4000 * self.zero_bank() as usize + addr as usize)
|
Address(0x4000 * self.zero_bank() as usize + addr as usize)
|
||||||
}
|
}
|
||||||
0x0000..=0x3FFF => Addr(addr as usize),
|
0x0000..=0x3FFF => Address(addr as usize),
|
||||||
0x4000..=0x7FFF => Addr(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000)),
|
0x4000..=0x7FFF => {
|
||||||
0xA000..=0xBFFF if self.mem_enabled => Byte(self.mem[self.ram_addr(addr)]),
|
Address(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000))
|
||||||
0xA000..=0xBFFF => Byte(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),
|
_ => unreachable!("A read from {:#06X} should not be handled by MBC1", addr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -270,9 +227,9 @@ impl MbcIo for Mbc1 {
|
||||||
}
|
}
|
||||||
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.mem_enabled => {
|
0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => {
|
||||||
let ram_addr = self.ram_addr(addr);
|
let ram_addr = self.ram_addr(addr);
|
||||||
self.mem[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),
|
||||||
|
@ -280,198 +237,55 @@ impl MbcIo for Mbc1 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RtcTick for Mbc1 {
|
|
||||||
fn tick(&mut self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Default, Clone, Copy)]
|
|
||||||
struct RtClock {
|
|
||||||
/// 6-bit unsigned integer
|
|
||||||
sec: u8,
|
|
||||||
/// 6-bit unsigned integer
|
|
||||||
min: u8,
|
|
||||||
/// 6-bit unsigned integer
|
|
||||||
hr: u8,
|
|
||||||
day_low: u8,
|
|
||||||
day_high: DayHigh,
|
|
||||||
|
|
||||||
cycles: Cycle,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RtClock {
|
|
||||||
fn inc_day(&mut self) {
|
|
||||||
// TODO: Figure out order of operations, the brackets are a bit too defensive here
|
|
||||||
let days: u16 = (((self.day_high.ninth() as u16) << 8) | self.day_low as u16) + 1;
|
|
||||||
|
|
||||||
if days > 0x1FF {
|
|
||||||
self.day_high.set_carry(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
self.day_high.set_ninth(((days >> 8) & 0x01) == 0x01);
|
|
||||||
self.day_low = days as u8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RtcTick for RtClock {
|
|
||||||
fn tick(&mut self) {
|
|
||||||
// This is the sort of situation where you'd want to use a scheduler.
|
|
||||||
if self.day_high.halt() {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.cycles += 1;
|
|
||||||
|
|
||||||
if self.cycles >= SM83_CLOCK_SPEED {
|
|
||||||
self.cycles %= SM83_CLOCK_SPEED;
|
|
||||||
|
|
||||||
self.sec += 1;
|
|
||||||
|
|
||||||
if self.sec == 60 {
|
|
||||||
self.sec = 0;
|
|
||||||
self.min += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.min == 60 {
|
|
||||||
self.min = 0;
|
|
||||||
self.hr += 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if self.hr == 24 {
|
|
||||||
self.hr = 0;
|
|
||||||
self.inc_day();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait RtcTick {
|
|
||||||
fn tick(&mut self);
|
|
||||||
}
|
|
||||||
|
|
||||||
bitfield! {
|
|
||||||
struct DayHigh(u8);
|
|
||||||
impl Debug;
|
|
||||||
_, set_carry: 7;
|
|
||||||
halt, _: 6;
|
|
||||||
ninth, set_ninth: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Copy for DayHigh {}
|
|
||||||
impl Clone for DayHigh {
|
|
||||||
fn clone(&self) -> Self {
|
|
||||||
*self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for DayHigh {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self(0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<u8> for DayHigh {
|
|
||||||
fn from(byte: u8) -> Self {
|
|
||||||
Self(byte)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<DayHigh> for u8 {
|
|
||||||
fn from(dh: DayHigh) -> Self {
|
|
||||||
dh.0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum Mbc3Device {
|
enum MBC3Device {
|
||||||
Ram,
|
ExternalRam,
|
||||||
Rtc(RtcRegister),
|
RealTimeClock,
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum RtcRegister {
|
|
||||||
Sec,
|
|
||||||
Min,
|
|
||||||
Hr,
|
|
||||||
DayLow,
|
|
||||||
DayHigh,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[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,
|
||||||
|
|
||||||
devs_enabled: bool,
|
devs_enabled: bool,
|
||||||
mapped: Option<Mbc3Device>,
|
mapped: Option<MBC3Device>,
|
||||||
mem: Vec<u8>,
|
memory: Vec<u8>,
|
||||||
|
|
||||||
// RTC Data Latch Previous Write
|
// RTC Data Latch Previous Write
|
||||||
prev_latch_write: Option<u8>,
|
prev_latch_write: Option<u8>,
|
||||||
|
|
||||||
hw: Mbc3Hardware,
|
|
||||||
rtc: RtClock,
|
|
||||||
rtc_latch: Option<RtClock>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mbc3 {
|
impl MBC3 {
|
||||||
fn new(hw: Mbc3Hardware, ram_cap: usize) -> Self {
|
fn new(ram_cap: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
mem: vec![0; ram_cap],
|
memory: vec![0; ram_cap],
|
||||||
rom_bank: Default::default(),
|
rom_bank: Default::default(),
|
||||||
ram_bank: Default::default(),
|
ram_bank: Default::default(),
|
||||||
devs_enabled: Default::default(),
|
devs_enabled: Default::default(),
|
||||||
mapped: Default::default(),
|
mapped: Default::default(),
|
||||||
prev_latch_write: Default::default(),
|
prev_latch_write: Default::default(),
|
||||||
rtc: Default::default(),
|
|
||||||
rtc_latch: Default::default(),
|
|
||||||
hw,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Savable for Mbc3 {
|
impl MBCIo for MBC3 {
|
||||||
fn ext_ram(&self) -> Option<&[u8]> {
|
fn handle_read(&self, addr: u16) -> MBCResult {
|
||||||
match self.hw {
|
use MBCResult::*;
|
||||||
Mbc3Hardware::BatteryRam | Mbc3Hardware::RtcBatteryRam => Some(&self.mem),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ext_ram_mut(&mut self) -> Option<&mut [u8]> {
|
|
||||||
match self.hw {
|
|
||||||
Mbc3Hardware::BatteryRam | Mbc3Hardware::RtcBatteryRam => Some(&mut self.mem),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MbcIo for Mbc3 {
|
|
||||||
fn handle_read(&self, addr: u16) -> MbcResult {
|
|
||||||
use MbcResult::*;
|
|
||||||
use RtcRegister::*;
|
|
||||||
|
|
||||||
let res = match addr {
|
let res = match addr {
|
||||||
0x0000..=0x3FFF => Addr(addr as usize),
|
0x0000..=0x3FFF => Address(addr as usize),
|
||||||
0x4000..=0x7FFF => Addr(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.mapped {
|
0xA000..=0xBFFF => match self.mapped {
|
||||||
Some(Mbc3Device::Ram) if self.devs_enabled => {
|
Some(MBC3Device::ExternalRam) if self.devs_enabled => {
|
||||||
Byte(self.mem[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::Rtc(reg)) if self.devs_enabled => Byte(
|
Some(MBC3Device::RealTimeClock) if self.devs_enabled => {
|
||||||
self.rtc_latch
|
todo!("Return Latched value of register")
|
||||||
.as_ref()
|
}
|
||||||
.map(|rtc| match reg {
|
_ => Value(0xFF),
|
||||||
Sec => rtc.sec,
|
|
||||||
Min => rtc.min,
|
|
||||||
Hr => rtc.hr,
|
|
||||||
DayLow => rtc.day_low,
|
|
||||||
DayHigh => rtc.day_high.into(),
|
|
||||||
})
|
|
||||||
.unwrap_or(0xFF),
|
|
||||||
),
|
|
||||||
_ => Byte(0xFF),
|
|
||||||
},
|
},
|
||||||
_ => unreachable!("A read from {:#06X} should not be handled by MBC3", addr),
|
_ => unreachable!("A read from {:#06X} should not be handled by MBC3", addr),
|
||||||
};
|
};
|
||||||
|
@ -480,8 +294,6 @@ impl MbcIo for Mbc3 {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn handle_write(&mut self, addr: u16, byte: u8) {
|
fn handle_write(&mut self, addr: u16, byte: u8) {
|
||||||
use RtcRegister::*;
|
|
||||||
|
|
||||||
match addr {
|
match addr {
|
||||||
0x000..=0x1FFF => self.devs_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 => {
|
||||||
|
@ -493,39 +305,28 @@ 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.mapped = Some(Mbc3Device::Ram);
|
self.mapped = Some(MBC3Device::ExternalRam);
|
||||||
|
}
|
||||||
|
0x08 | 0x09 | 0x0A | 0x0B | 0x0C => {
|
||||||
|
self.mapped = Some(MBC3Device::RealTimeClock);
|
||||||
}
|
}
|
||||||
0x08 => self.mapped = Some(Mbc3Device::Rtc(Sec)),
|
|
||||||
0x09 => self.mapped = Some(Mbc3Device::Rtc(Min)),
|
|
||||||
0x0A => self.mapped = Some(Mbc3Device::Rtc(Hr)),
|
|
||||||
0x0B => self.mapped = Some(Mbc3Device::Rtc(DayLow)),
|
|
||||||
0x0C => self.mapped = Some(Mbc3Device::Rtc(DayHigh)),
|
|
||||||
|
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
0x6000..=0x7FFF => {
|
0x6000..=0x7FFF => {
|
||||||
if let Some(0x00) = self.prev_latch_write {
|
if let Some(0x00) = self.prev_latch_write {
|
||||||
if byte == 0x01 {
|
if byte == 0x01 {
|
||||||
self.rtc_latch = Some(self.rtc);
|
todo!("Perform Data Latch")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.prev_latch_write = Some(byte);
|
self.prev_latch_write = Some(byte);
|
||||||
}
|
}
|
||||||
0xA000..=0xBFFF => match self.mapped {
|
0xA000..=0xBFFF => match self.mapped {
|
||||||
Some(Mbc3Device::Ram) if self.devs_enabled => {
|
Some(MBC3Device::ExternalRam) if self.devs_enabled => {
|
||||||
self.mem[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.devs_enabled => {
|
||||||
|
todo!("Write to RTC")
|
||||||
}
|
}
|
||||||
Some(Mbc3Device::Rtc(rtc_reg)) if self.devs_enabled => match rtc_reg {
|
|
||||||
Sec => {
|
|
||||||
self.rtc.sec = byte & 0x3F;
|
|
||||||
// Writing to RTC S resets the internal sub-second counter
|
|
||||||
self.rtc.cycles = 0;
|
|
||||||
}
|
|
||||||
Min => self.rtc.min = byte & 0x3F,
|
|
||||||
Hr => self.rtc.hr = byte & 0x1F,
|
|
||||||
DayLow => self.rtc.day_low = byte,
|
|
||||||
DayHigh => self.rtc.day_high = (byte & 0xC1).into(),
|
|
||||||
},
|
|
||||||
_ => {}
|
_ => {}
|
||||||
},
|
},
|
||||||
_ => unreachable!("A write to {:#06X} should not be handled by MBC3", addr),
|
_ => unreachable!("A write to {:#06X} should not be handled by MBC3", addr),
|
||||||
|
@ -533,37 +334,27 @@ impl MbcIo for Mbc3 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RtcTick for Mbc3 {
|
|
||||||
fn tick(&mut self) {
|
|
||||||
if let Mbc3Hardware::RtcBatteryRam | Mbc3Hardware::Rtc = self.hw {
|
|
||||||
self.rtc.tick();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Mbc5 {
|
struct MBC5 {
|
||||||
/// 9-bit number
|
/// 9-bit number
|
||||||
rom_bank: u16,
|
rom_bank: u16,
|
||||||
/// 4-bit number
|
/// 4-bit number
|
||||||
ram_bank: u8,
|
ram_bank: u8,
|
||||||
|
|
||||||
rom_cap: usize,
|
rom_cap: usize,
|
||||||
mem: Vec<u8>,
|
|
||||||
mem_enabled: bool,
|
|
||||||
|
|
||||||
hw: Mbc5Hardware,
|
memory: Vec<u8>,
|
||||||
|
mem_enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mbc5 {
|
impl MBC5 {
|
||||||
fn new(hw: Mbc5Hardware, ram_cap: usize, rom_cap: usize) -> Self {
|
fn new(ram_cap: usize, rom_cap: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
rom_bank: 0x01,
|
rom_bank: 0x01,
|
||||||
mem: vec![0; ram_cap],
|
memory: vec![0; ram_cap],
|
||||||
rom_cap,
|
rom_cap,
|
||||||
ram_bank: Default::default(),
|
ram_bank: Default::default(),
|
||||||
mem_enabled: Default::default(),
|
mem_enabled: Default::default(),
|
||||||
hw,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -572,33 +363,17 @@ impl Mbc5 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Savable for Mbc5 {
|
impl MBCIo for MBC5 {
|
||||||
fn ext_ram(&self) -> Option<&[u8]> {
|
fn handle_read(&self, addr: u16) -> MBCResult {
|
||||||
match self.hw {
|
use MBCResult::*;
|
||||||
Mbc5Hardware::RumbleBatteryRam | Mbc5Hardware::BatteryRam => Some(&self.mem),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ext_ram_mut(&mut self) -> Option<&mut [u8]> {
|
|
||||||
match self.hw {
|
|
||||||
Mbc5Hardware::RumbleBatteryRam | Mbc5Hardware::BatteryRam => Some(&mut self.mem),
|
|
||||||
_ => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MbcIo for Mbc5 {
|
|
||||||
fn handle_read(&self, addr: u16) -> MbcResult {
|
|
||||||
use MbcResult::*;
|
|
||||||
|
|
||||||
match addr {
|
match addr {
|
||||||
0x0000..=0x3FFF => Addr(addr as usize),
|
0x0000..=0x3FFF => Address(addr as usize),
|
||||||
0x4000..=0x7FFF => Addr(self.bank_addr(addr)),
|
0x4000..=0x7FFF => Address(self.bank_addr(addr)),
|
||||||
0xA000..=0xBFFF if self.mem_enabled => {
|
0xA000..=0xBFFF if self.mem_enabled => {
|
||||||
Byte(self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
|
Value(self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
|
||||||
}
|
}
|
||||||
0xA000..=0xBFFF => Byte(0xFF),
|
0xA000..=0xBFFF => Value(0xFF),
|
||||||
_ => unreachable!("A read from {:#06X} should not be handled by MBC5", addr),
|
_ => unreachable!("A read from {:#06X} should not be handled by MBC5", addr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -610,7 +385,7 @@ impl MbcIo for Mbc5 {
|
||||||
0x3000..=0x3FFF => self.rom_bank = (self.rom_bank & 0x00FF) | (byte as u16 & 0x01) << 8,
|
0x3000..=0x3FFF => self.rom_bank = (self.rom_bank & 0x00FF) | (byte as u16 & 0x01) << 8,
|
||||||
0x4000..=0x5FFF => self.ram_bank = byte & 0x0F,
|
0x4000..=0x5FFF => self.ram_bank = byte & 0x0F,
|
||||||
0xA000..=0xBFFF if self.mem_enabled => {
|
0xA000..=0xBFFF if self.mem_enabled => {
|
||||||
self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte;
|
self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte;
|
||||||
}
|
}
|
||||||
0xA000..=0xBFFF => {}
|
0xA000..=0xBFFF => {}
|
||||||
_ => unreachable!("A write to {:#06X} should not be handled by MBC5", addr),
|
_ => unreachable!("A write to {:#06X} should not be handled by MBC5", addr),
|
||||||
|
@ -618,31 +393,25 @@ impl MbcIo for Mbc5 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RtcTick for Mbc5 {
|
|
||||||
fn tick(&mut self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct Mbc2 {
|
struct MBC2 {
|
||||||
/// 4-bit number
|
/// 4-bit number
|
||||||
rom_bank: u8,
|
rom_bank: u8,
|
||||||
mem: Box<[u8; Self::RAM_SIZE]>,
|
memory: Box<[u8; Self::RAM_SIZE]>,
|
||||||
|
|
||||||
mem_enabled: bool,
|
mem_enabled: bool,
|
||||||
|
|
||||||
rom_cap: usize,
|
rom_cap: usize,
|
||||||
hw: Mbc2Hardware,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Mbc2 {
|
impl MBC2 {
|
||||||
const RAM_SIZE: usize = 0x0200;
|
const RAM_SIZE: usize = 0x0200;
|
||||||
|
|
||||||
fn new(hw: Mbc2Hardware, rom_cap: usize) -> Self {
|
fn new(rom_cap: usize) -> Self {
|
||||||
Self {
|
Self {
|
||||||
rom_bank: 0x01,
|
rom_bank: 0x01,
|
||||||
mem: Box::new([0; Self::RAM_SIZE]),
|
memory: Box::new([0; Self::RAM_SIZE]),
|
||||||
rom_cap,
|
|
||||||
mem_enabled: Default::default(),
|
mem_enabled: Default::default(),
|
||||||
hw,
|
rom_cap,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -651,34 +420,18 @@ impl Mbc2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Savable for Mbc2 {
|
impl MBCIo for MBC2 {
|
||||||
fn ext_ram(&self) -> Option<&[u8]> {
|
fn handle_read(&self, addr: u16) -> MBCResult {
|
||||||
match self.hw {
|
use MBCResult::*;
|
||||||
Mbc2Hardware::BatteryRam => Some(self.mem.as_ref()),
|
|
||||||
Mbc2Hardware::None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ext_ram_mut(&mut self) -> Option<&mut [u8]> {
|
|
||||||
match self.hw {
|
|
||||||
Mbc2Hardware::BatteryRam => Some(self.mem.as_mut()),
|
|
||||||
Mbc2Hardware::None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MbcIo for Mbc2 {
|
|
||||||
fn handle_read(&self, addr: u16) -> MbcResult {
|
|
||||||
use MbcResult::*;
|
|
||||||
|
|
||||||
match addr {
|
match addr {
|
||||||
0x0000..=0x3FFF => Addr(addr as usize),
|
0x0000..=0x3FFF => Address(addr as usize),
|
||||||
0x4000..=0x7FFF => Addr(self.rom_addr(addr)),
|
0x4000..=0x7FFF => Address(self.rom_addr(addr)),
|
||||||
0xA000..=0xBFFF if self.mem_enabled => {
|
0xA000..=0xBFFF if self.mem_enabled => {
|
||||||
let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1);
|
let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1);
|
||||||
Byte(self.mem[mbc2_addr] | 0xF0)
|
Value(self.memory[mbc2_addr] | 0xF0)
|
||||||
}
|
}
|
||||||
0xA000..=0xBFFF => Byte(0xFF),
|
0xA000..=0xBFFF => Value(0xFF),
|
||||||
_ => unreachable!("A read from {:#06X} should not be handled by MBC2", addr),
|
_ => unreachable!("A read from {:#06X} should not be handled by MBC2", addr),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -693,7 +446,7 @@ impl MbcIo for Mbc2 {
|
||||||
0x0000..=0x3FFF => self.mem_enabled = nybble == 0x0A,
|
0x0000..=0x3FFF => self.mem_enabled = nybble == 0x0A,
|
||||||
0xA000..=0xBFFF if self.mem_enabled => {
|
0xA000..=0xBFFF if self.mem_enabled => {
|
||||||
let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1);
|
let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1);
|
||||||
self.mem[mbc2_addr] = nybble;
|
self.memory[mbc2_addr] = nybble;
|
||||||
}
|
}
|
||||||
0x4000..=0x7FFF | 0xA000..=0xBFFF => {}
|
0x4000..=0x7FFF | 0xA000..=0xBFFF => {}
|
||||||
_ => unreachable!("A write to {:#06X} should not be handled by MBC2", addr),
|
_ => unreachable!("A write to {:#06X} should not be handled by MBC2", addr),
|
||||||
|
@ -701,87 +454,47 @@ impl MbcIo for Mbc2 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RtcTick for Mbc2 {
|
|
||||||
fn tick(&mut self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
struct NoMbc;
|
struct NoMBC;
|
||||||
|
|
||||||
impl Savable for NoMbc {
|
impl MBCIo for NoMBC {
|
||||||
fn ext_ram(&self) -> Option<&[u8]> {
|
fn handle_read(&self, addr: u16) -> MBCResult {
|
||||||
None
|
MBCResult::Address(addr as usize)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ext_ram_mut(&mut self) -> Option<&mut [u8]> {
|
fn handle_write(&mut self, _addr: u16, _byte: u8) {
|
||||||
None
|
// eprintln!("Tried to write {:#04X} to a read-only cartridge", byte);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl MbcIo for NoMbc {
|
trait MBCIo {
|
||||||
fn handle_read(&self, addr: u16) -> MbcResult {
|
fn handle_read(&self, addr: u16) -> MBCResult;
|
||||||
MbcResult::Addr(addr as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_write(&mut self, _: u16, byte: u8) {
|
|
||||||
tracing::warn!("Attempted write of {:#04X} to cartridge w/out MBC", byte);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RtcTick for NoMbc {
|
|
||||||
fn tick(&mut self) {}
|
|
||||||
}
|
|
||||||
|
|
||||||
trait MbcIo: Savable + RtcTick {
|
|
||||||
fn handle_read(&self, addr: u16) -> MbcResult;
|
|
||||||
fn handle_write(&mut self, addr: u16, byte: u8);
|
fn handle_write(&mut self, addr: u16, byte: u8);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum MbcResult {
|
enum MBCResult {
|
||||||
Addr(usize),
|
Address(usize),
|
||||||
Byte(u8),
|
Value(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
enum MbcKind {
|
enum MBCKind {
|
||||||
None,
|
None,
|
||||||
Mbc1(Mbc1Hardware),
|
MBC1,
|
||||||
Mbc2(Mbc2Hardware),
|
MBC1WithBattery,
|
||||||
Mbc3(Mbc3Hardware),
|
MBC2,
|
||||||
Mbc5(Mbc5Hardware),
|
MBC2WithBattery,
|
||||||
|
MBC3,
|
||||||
|
MBC3WithBattery,
|
||||||
|
MBC5,
|
||||||
|
MBC5WithBattery,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
impl Default for MBCKind {
|
||||||
enum Mbc1Hardware {
|
fn default() -> Self {
|
||||||
None,
|
Self::None
|
||||||
Ram,
|
}
|
||||||
BatteryRam,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum Mbc2Hardware {
|
|
||||||
None,
|
|
||||||
BatteryRam,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum Mbc3Hardware {
|
|
||||||
Rtc,
|
|
||||||
RtcBatteryRam,
|
|
||||||
None,
|
|
||||||
Ram,
|
|
||||||
BatteryRam,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
enum Mbc5Hardware {
|
|
||||||
None,
|
|
||||||
Ram,
|
|
||||||
BatteryRam,
|
|
||||||
Rumble,
|
|
||||||
RumbleRam,
|
|
||||||
RumbleBatteryRam,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
@ -809,6 +522,12 @@ impl RamSize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for RamSize {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for RamSize {
|
impl From<u8> for RamSize {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
use RamSize::*;
|
use RamSize::*;
|
||||||
|
@ -877,90 +596,14 @@ impl From<u8> for RomSize {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Box<dyn MbcIo> {
|
impl std::fmt::Debug for Box<dyn MBCIo> {
|
||||||
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
todo!("Implement Debug for Box<dyn MBC> Trait Object");
|
todo!("Implement Debug for Box<dyn MBC> Trait Object");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
trait Savable {
|
impl Default for Box<dyn MBCIo> {
|
||||||
fn ext_ram(&self) -> Option<&[u8]>;
|
fn default() -> Self {
|
||||||
fn ext_ram_mut(&mut self) -> Option<&mut [u8]>;
|
Box::new(NoMBC)
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::Cartridge;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn empty_rom_title() {
|
|
||||||
let title = [
|
|
||||||
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00,
|
|
||||||
];
|
|
||||||
|
|
||||||
assert_eq!(None, Cartridge::detect_title(&title));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn normal_rom_title() {
|
|
||||||
let title = [
|
|
||||||
0x50, 0x4F, 0x4B, 0x45, 0x4D, 0x4F, 0x4E, 0x20, 0x42, 0x4C, 0x55, 0x45, 0x00, 0x00,
|
|
||||||
0x00, 0x00,
|
|
||||||
];
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Some(String::from("POKEMON BLUE")),
|
|
||||||
Cartridge::detect_title(&title)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn extra_spaces_title() {
|
|
||||||
let title = [
|
|
||||||
0x54, 0x4f, 0x4b, 0x49, 0x4d, 0x45, 0x4b, 0x49, 0x20, 0x43, 0x55, 0x4c, 0x20, 0x20, 0,
|
|
||||||
0,
|
|
||||||
];
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Some(String::from("TOKIMEKI CUL")),
|
|
||||||
Cartridge::detect_title(&title)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn long_title() {
|
|
||||||
let title = [
|
|
||||||
0x54, 0x4f, 0x4b, 0x49, 0x4d, 0x45, 0x4b, 0x49, 0x20, 0x43, 0x55, 0x4c, 0x54, 0x55,
|
|
||||||
0x52, 0x45,
|
|
||||||
];
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Some(String::from("TOKIMEKI CULTURE")),
|
|
||||||
Cartridge::detect_title(&title),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn publisher_code_and_title() {
|
|
||||||
let title: [u8; 16] = [
|
|
||||||
0x47, 0x52, 0x41, 0x4E, 0x44, 0x20, 0x54, 0x48, 0x45, 0x46, 0x54, 0x41, 0x4F, 0x41,
|
|
||||||
0x45, 0x80,
|
|
||||||
];
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
Some(String::from("GRAND THEFT")),
|
|
||||||
Cartridge::detect_title(&title)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn all_whitespace_title() {
|
|
||||||
let title = [
|
|
||||||
0x20, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
|
|
||||||
0x00, 0x00,
|
|
||||||
];
|
|
||||||
|
|
||||||
assert_eq!(None, Cartridge::detect_title(&title));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
113
src/cpu.rs
113
src/cpu.rs
|
@ -5,9 +5,9 @@ use crate::Cycle;
|
||||||
use bitfield::bitfield;
|
use bitfield::bitfield;
|
||||||
use std::fmt::{Display, Formatter, Result as FmtResult};
|
use std::fmt::{Display, Formatter, Result as FmtResult};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Cpu {
|
pub struct Cpu {
|
||||||
pub(crate) bus: Bus,
|
pub bus: Bus,
|
||||||
reg: Registers,
|
reg: Registers,
|
||||||
flags: Flags,
|
flags: Flags,
|
||||||
ime: ImeState,
|
ime: ImeState,
|
||||||
|
@ -15,13 +15,28 @@ pub struct Cpu {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cpu {
|
impl Cpu {
|
||||||
pub(crate) fn new(rom: [u8; BOOT_SIZE]) -> Self {
|
pub(crate) fn without_boot() -> Self {
|
||||||
|
Self {
|
||||||
|
reg: Registers {
|
||||||
|
a: 0x01,
|
||||||
|
b: 0x00,
|
||||||
|
c: 0x13,
|
||||||
|
d: 0x00,
|
||||||
|
e: 0xD8,
|
||||||
|
h: 0x01,
|
||||||
|
l: 0x4D,
|
||||||
|
sp: 0xFFFE,
|
||||||
|
pc: 0x0100,
|
||||||
|
},
|
||||||
|
flags: 0xb0.into(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn with_boot(rom: [u8; BOOT_SIZE]) -> Self {
|
||||||
Self {
|
Self {
|
||||||
bus: Bus::with_boot(rom),
|
bus: Bus::with_boot(rom),
|
||||||
reg: Default::default(),
|
..Default::default()
|
||||||
flags: Flags(0),
|
|
||||||
ime: ImeState::Disabled,
|
|
||||||
state: State::Execute,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +57,10 @@ impl Cpu {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn is_halted(&self) -> bool {
|
pub(crate) fn is_halted(&self) -> bool {
|
||||||
matches!(self.state, State::Halt(_))
|
match self.state {
|
||||||
|
State::Halt(_) => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn halt_kind(&self) -> Option<HaltKind> {
|
pub(crate) fn halt_kind(&self) -> Option<HaltKind> {
|
||||||
|
@ -53,12 +71,6 @@ impl Cpu {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Cpu {
|
|
||||||
fn default() -> Self {
|
|
||||||
Cpu::new(*include_bytes!("../bin/bootix_dmg.bin"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Cpu {
|
impl Cpu {
|
||||||
/// Fetch an [Instruction] from the memory bus
|
/// Fetch an [Instruction] from the memory bus
|
||||||
/// (4 cycles)
|
/// (4 cycles)
|
||||||
|
@ -93,7 +105,7 @@ impl Cpu {
|
||||||
/// routine.
|
/// routine.
|
||||||
///
|
///
|
||||||
/// Handle HALT and interrupts.
|
/// Handle HALT and interrupts.
|
||||||
pub(crate) fn step(&mut self) -> Cycle {
|
pub fn step(&mut self) -> Cycle {
|
||||||
// Log instructions
|
// Log instructions
|
||||||
// if self.reg.pc > 0xFF {
|
// if self.reg.pc > 0xFF {
|
||||||
// let out = std::io::stdout();
|
// let out = std::io::stdout();
|
||||||
|
@ -108,10 +120,13 @@ impl Cpu {
|
||||||
use HaltKind::*;
|
use HaltKind::*;
|
||||||
|
|
||||||
self.bus.clock();
|
self.bus.clock();
|
||||||
return match kind {
|
|
||||||
|
let elapsed = match kind {
|
||||||
ImeEnabled | NonePending => 4,
|
ImeEnabled | NonePending => 4,
|
||||||
SomePending => todo!("Implement HALT bug"),
|
SomePending => todo!("Implement HALT bug"),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
return elapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
let opcode = self.fetch();
|
let opcode = self.fetch();
|
||||||
|
@ -120,11 +135,11 @@ impl Cpu {
|
||||||
self.handle_ei();
|
self.handle_ei();
|
||||||
|
|
||||||
// For use in Blargg's Test ROMs
|
// For use in Blargg's Test ROMs
|
||||||
// if self.read_byte(0xFF02) == 0x81 {
|
if self.read_byte(0xFF02) == 0x81 {
|
||||||
// let c = self.read_byte(0xFF01) as char;
|
let c = self.read_byte(0xFF01) as char;
|
||||||
// self.write_byte(0xFF02, 0x00);
|
self.write_byte(0xFF02, 0x00);
|
||||||
// eprint!("{}", c);
|
eprint!("{}", c);
|
||||||
// }
|
}
|
||||||
|
|
||||||
elapsed
|
elapsed
|
||||||
}
|
}
|
||||||
|
@ -141,6 +156,14 @@ impl BusIo for Cpu {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cpu {
|
impl Cpu {
|
||||||
|
pub(crate) fn bus(&self) -> &Bus {
|
||||||
|
&self.bus
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn bus_mut(&mut self) -> &mut Bus {
|
||||||
|
&mut self.bus
|
||||||
|
}
|
||||||
|
|
||||||
fn handle_ei(&mut self) {
|
fn handle_ei(&mut self) {
|
||||||
match self.ime {
|
match self.ime {
|
||||||
ImeState::EiExecuted => self.ime = ImeState::Pending,
|
ImeState::EiExecuted => self.ime = ImeState::Pending,
|
||||||
|
@ -238,6 +261,12 @@ enum State {
|
||||||
// Stop,
|
// Stop,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for State {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Execute
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Cpu {
|
impl Cpu {
|
||||||
pub(crate) fn set_register(&mut self, register: Register, value: u8) {
|
pub(crate) fn set_register(&mut self, register: Register, value: u8) {
|
||||||
use Register::*;
|
use Register::*;
|
||||||
|
@ -431,6 +460,12 @@ impl Clone for Flags {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for Flags {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for Flags {
|
impl Display for Flags {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
|
||||||
if self.z() {
|
if self.z() {
|
||||||
|
@ -486,38 +521,8 @@ pub(crate) enum ImeState {
|
||||||
Enabled,
|
Enabled,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) mod dbg {
|
impl Default for ImeState {
|
||||||
use super::{Cpu, ImeState, RegisterPair};
|
fn default() -> Self {
|
||||||
|
Self::Disabled
|
||||||
pub(crate) fn flags(cpu: &Cpu) -> u8 {
|
|
||||||
cpu.flags.into()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn af(cpu: &Cpu) -> u16 {
|
|
||||||
cpu.register_pair(RegisterPair::AF)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn bc(cpu: &Cpu) -> u16 {
|
|
||||||
cpu.register_pair(RegisterPair::BC)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn de(cpu: &Cpu) -> u16 {
|
|
||||||
cpu.register_pair(RegisterPair::DE)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn hl(cpu: &Cpu) -> u16 {
|
|
||||||
cpu.register_pair(RegisterPair::HL)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn sp(cpu: &Cpu) -> u16 {
|
|
||||||
cpu.register_pair(RegisterPair::SP)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn pc(cpu: &Cpu) -> u16 {
|
|
||||||
cpu.register_pair(RegisterPair::PC)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn ime(cpu: &Cpu) -> bool {
|
|
||||||
matches!(cpu.ime, ImeState::Enabled)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
218
src/emu.rs
218
src/emu.rs
|
@ -1,163 +1,121 @@
|
||||||
use crate::apu::gen::SampleProducer;
|
use crate::apu::gen::SampleProducer;
|
||||||
use crate::bus::BOOT_SIZE;
|
|
||||||
use crate::cartridge::Cartridge;
|
|
||||||
use crate::cpu::Cpu;
|
use crate::cpu::Cpu;
|
||||||
|
use crate::joypad::{self, Joypad};
|
||||||
use crate::{Cycle, GB_HEIGHT, GB_WIDTH};
|
use crate::{Cycle, GB_HEIGHT, GB_WIDTH};
|
||||||
use clap::crate_name;
|
|
||||||
use gilrs::Gilrs;
|
use gilrs::Gilrs;
|
||||||
use std::fs::File;
|
|
||||||
use std::io::{Read, Write};
|
|
||||||
use std::path::{Path, PathBuf};
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
use thiserror::Error;
|
use winit_input_helper::WinitInputHelper;
|
||||||
use winit::event::KeyboardInput;
|
|
||||||
use winit::event_loop::ControlFlow;
|
|
||||||
|
|
||||||
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 = 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 = "Game Boy Screen";
|
const DEFAULT_TITLE: &str = "DMG-01 Emulator";
|
||||||
|
|
||||||
#[inline]
|
pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: &WinitInputHelper) -> Cycle {
|
||||||
pub fn run_frame(cpu: &mut Cpu, gamepad: &mut Gilrs, key: KeyboardInput) {
|
|
||||||
run(cpu, gamepad, key, CYCLES_IN_FRAME)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn run(cpu: &mut Cpu, gamepad: &mut Gilrs, key: KeyboardInput, cycles: Cycle) {
|
|
||||||
let mut elapsed = 0;
|
let mut elapsed = 0;
|
||||||
|
|
||||||
if let Some(event) = gamepad.next_event() {
|
if let Some(event) = gamepad.next_event() {
|
||||||
crate::joypad::handle_gamepad_input(&mut cpu.bus.joyp, event);
|
joypad::handle_gamepad_input(emu.joyp_mut(), event);
|
||||||
}
|
}
|
||||||
crate::joypad::handle_keyboard_input(&mut cpu.bus.joyp, key);
|
|
||||||
|
|
||||||
while elapsed < cycles {
|
joypad::handle_keyboard_input(emu.joyp_mut(), key);
|
||||||
elapsed += cpu.step();
|
while elapsed < CYCLES_IN_FRAME {
|
||||||
|
elapsed += emu.step();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
elapsed
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn save_and_exit(cpu: &Cpu, control_flow: &mut ControlFlow) {
|
pub fn draw_frame(emu: &Emulator, buf: &mut [u8; GB_HEIGHT * GB_WIDTH * 4]) {
|
||||||
write_save(cpu);
|
buf.copy_from_slice(emu.cpu.bus().ppu.frame_buf());
|
||||||
*control_flow = ControlFlow::Exit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub struct Emulator {
|
||||||
pub fn pixel_buf(cpu: &Cpu) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] {
|
cpu: Cpu,
|
||||||
use crate::ppu::Device;
|
timestamp: Cycle,
|
||||||
cpu.bus.ppu.frame_buf.get(Device::Host)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn from_boot_rom<P: AsRef<Path>>(path: P) -> std::io::Result<Cpu> {
|
impl Emulator {
|
||||||
Ok(Cpu::new(read_boot(path)?))
|
fn new(cpu: Cpu) -> Self {
|
||||||
}
|
Self {
|
||||||
|
cpu,
|
||||||
pub fn read_game_rom<P: AsRef<Path>>(cpu: &mut Cpu, path: P) -> std::io::Result<()> {
|
timestamp: Default::default(),
|
||||||
cpu.bus.cart = Some(Cartridge::new(std::fs::read(path.as_ref())?));
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_audio_prod(cpu: &mut Cpu, prod: SampleProducer<f32>) {
|
|
||||||
cpu.bus.apu.prod = Some(prod);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rom_title(cpu: &Cpu) -> &str {
|
|
||||||
cpu.bus
|
|
||||||
.cart
|
|
||||||
.as_ref()
|
|
||||||
.and_then(|c| c.title.as_deref())
|
|
||||||
.unwrap_or(DEFAULT_TITLE)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn write_save(cpu: &Cpu) {
|
|
||||||
match cpu.bus.cart.as_ref() {
|
|
||||||
Some(cart) => match write_save_to_file(cart) {
|
|
||||||
Ok(path) => tracing::info!("Wrote to save at {:?}", path),
|
|
||||||
Err(err @ SaveError::NotApplicable) => tracing::warn!("Unable to Save: {:?}", err),
|
|
||||||
Err(SaveError::DiffSize) => unreachable!(),
|
|
||||||
Err(SaveError::Io(err)) => tracing::error!("{:?}", err),
|
|
||||||
},
|
|
||||||
None => tracing::error!("No cartridge is currently present"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn load_save(cpu: &mut Cpu) {
|
|
||||||
match cpu.bus.cart.as_mut() {
|
|
||||||
Some(cart) => match read_save_from_file(cart) {
|
|
||||||
Ok(path) => tracing::info!("Loaded save from {:?}", path),
|
|
||||||
Err(err @ SaveError::NotApplicable) => tracing::warn!("Unable to load save: {}", err),
|
|
||||||
Err(err @ SaveError::DiffSize) => tracing::error!("Unable to load save: {}", err),
|
|
||||||
Err(SaveError::Io(err)) => match err.kind() {
|
|
||||||
std::io::ErrorKind::NotFound => tracing::warn!("Save not found"),
|
|
||||||
_ => tracing::error!("{:?}", err),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
None => tracing::error!("No cartridge is currently present"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_save_to_file(cart: &Cartridge) -> Result<PathBuf, SaveError> {
|
|
||||||
match cart.title.as_ref().zip(cart.ext_ram()) {
|
|
||||||
Some((title, ram)) => {
|
|
||||||
let mut save_path = data_path().unwrap_or_else(|| PathBuf::from("."));
|
|
||||||
save_path.push(title);
|
|
||||||
save_path.set_extension("sav");
|
|
||||||
|
|
||||||
let mut file = File::create(&save_path)?;
|
|
||||||
file.write_all(ram)?;
|
|
||||||
Ok(save_path)
|
|
||||||
}
|
}
|
||||||
None => Err(SaveError::NotApplicable),
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_save_from_file(cart: &mut Cartridge) -> Result<PathBuf, SaveError> {
|
pub mod build {
|
||||||
match cart.title.clone().zip(cart.ext_ram_mut()) {
|
use std::fs::File;
|
||||||
Some((title, ext_ram)) => {
|
use std::io::{Read, Result};
|
||||||
let mut save_path = data_path().unwrap_or_else(|| PathBuf::from("."));
|
use std::path::Path;
|
||||||
save_path.push(title);
|
|
||||||
save_path.set_extension("sav");
|
|
||||||
|
|
||||||
let mut file = File::open(&save_path)?;
|
use crate::bus::BOOT_SIZE;
|
||||||
let mut memory = Vec::new();
|
use crate::cpu::Cpu;
|
||||||
file.read_to_end(&mut memory)?;
|
|
||||||
|
|
||||||
if ext_ram.len() != memory.len() {
|
use super::Emulator;
|
||||||
return Err(SaveError::DiffSize);
|
|
||||||
|
#[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)
|
||||||
}
|
}
|
||||||
|
|
||||||
ext_ram.copy_from_slice(&memory);
|
emu
|
||||||
Ok(save_path)
|
|
||||||
}
|
}
|
||||||
None => Err(SaveError::NotApplicable),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_boot<P: AsRef<Path>>(path: P) -> std::io::Result<[u8; BOOT_SIZE]> {
|
|
||||||
let mut buf = [0; BOOT_SIZE];
|
|
||||||
let mut file = File::open(path.as_ref())?;
|
|
||||||
|
|
||||||
file.read_exact(&mut buf)?;
|
|
||||||
Ok(buf)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn data_path() -> Option<PathBuf> {
|
|
||||||
match directories_next::ProjectDirs::from("dev", "musuka", crate_name!()) {
|
|
||||||
Some(dirs) => {
|
|
||||||
let data_local = dirs.data_local_dir();
|
|
||||||
std::fs::create_dir_all(data_local).ok()?;
|
|
||||||
Some(data_local.to_path_buf())
|
|
||||||
}
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
|
||||||
pub enum SaveError {
|
|
||||||
#[error("cartridge lacks title and/or external ram")]
|
|
||||||
NotApplicable,
|
|
||||||
#[error(transparent)]
|
|
||||||
Io(#[from] std::io::Error),
|
|
||||||
#[error("save file size differs from external ram")]
|
|
||||||
DiffSize,
|
|
||||||
}
|
|
||||||
|
|
472
src/gui.rs
472
src/gui.rs
|
@ -1,472 +0,0 @@
|
||||||
use egui::{Context, TextureId};
|
|
||||||
use egui_wgpu_backend::{wgpu, RenderPass, ScreenDescriptor};
|
|
||||||
use egui_winit_platform::Platform;
|
|
||||||
use wgpu::{
|
|
||||||
Adapter, CommandEncoder, Device, Extent3d, FilterMode, Instance, Queue, RequestDeviceError,
|
|
||||||
Surface, SurfaceCapabilities, SurfaceConfiguration, Texture, TextureView,
|
|
||||||
TextureViewDescriptor,
|
|
||||||
};
|
|
||||||
|
|
||||||
use winit::dpi::PhysicalSize;
|
|
||||||
use winit::error::OsError;
|
|
||||||
use winit::event::{ElementState, Event, KeyboardInput};
|
|
||||||
use winit::event_loop::{ControlFlow, EventLoop};
|
|
||||||
use winit::window::Window;
|
|
||||||
|
|
||||||
use crate::cpu::Cpu;
|
|
||||||
use crate::{emu, GB_HEIGHT, GB_WIDTH};
|
|
||||||
|
|
||||||
const EGUI_DIMENSIONS: (usize, usize) = (1280, 720);
|
|
||||||
const FILTER_MODE: FilterMode = FilterMode::Nearest;
|
|
||||||
const WINDOW_TITLE: &str = "DMG-01 Emulator";
|
|
||||||
|
|
||||||
const SCALE: f32 = 3.0;
|
|
||||||
|
|
||||||
pub struct Gui {
|
|
||||||
pub should_quit: bool,
|
|
||||||
pub title: String,
|
|
||||||
pub mode: EmuMode,
|
|
||||||
|
|
||||||
window: Window,
|
|
||||||
platform: Platform,
|
|
||||||
|
|
||||||
surface: Surface,
|
|
||||||
device: Device,
|
|
||||||
queue: Queue,
|
|
||||||
surface_config: SurfaceConfiguration,
|
|
||||||
render_pass: RenderPass,
|
|
||||||
|
|
||||||
texture: Texture,
|
|
||||||
texture_size: Extent3d,
|
|
||||||
texture_id: TextureId,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Gui {
|
|
||||||
pub fn new<T>(title: String, event_loop: &EventLoop<T>, cpu: &Cpu) -> Self {
|
|
||||||
use wgpu::InstanceDescriptor;
|
|
||||||
|
|
||||||
let window = build_window(event_loop).expect("build window");
|
|
||||||
|
|
||||||
let instance = Instance::new(InstanceDescriptor::default());
|
|
||||||
let surface = unsafe {
|
|
||||||
instance
|
|
||||||
.create_surface(&window)
|
|
||||||
.expect("create wgpu instance surface")
|
|
||||||
};
|
|
||||||
|
|
||||||
let adapter = request_adapter(&instance, &surface).expect("request adaptor");
|
|
||||||
let (device, queue) = request_device(&adapter).expect("request device");
|
|
||||||
|
|
||||||
let capabilities = surface.get_capabilities(&adapter);
|
|
||||||
|
|
||||||
let surface_config = surface_config(&window, capabilities);
|
|
||||||
surface.configure(&device, &surface_config);
|
|
||||||
let platform = platform(&window);
|
|
||||||
let mut render_pass = RenderPass::new(&device, surface_config.format, 1);
|
|
||||||
|
|
||||||
let texture_size = texture_size();
|
|
||||||
let texture = create_texture(&device, texture_size);
|
|
||||||
write_to_texture(&queue, &texture, emu::pixel_buf(cpu), texture_size);
|
|
||||||
|
|
||||||
let view = texture.create_view(&TextureViewDescriptor::default());
|
|
||||||
let texture_id = expose_texture_to_egui(&mut render_pass, &device, &view);
|
|
||||||
|
|
||||||
Self {
|
|
||||||
should_quit: Default::default(),
|
|
||||||
title,
|
|
||||||
mode: EmuMode::Running,
|
|
||||||
|
|
||||||
window,
|
|
||||||
platform,
|
|
||||||
|
|
||||||
surface,
|
|
||||||
device,
|
|
||||||
queue,
|
|
||||||
surface_config,
|
|
||||||
render_pass,
|
|
||||||
|
|
||||||
texture,
|
|
||||||
texture_size,
|
|
||||||
texture_id,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn maybe_quit(&self, cpu: &Cpu, control_flow: &mut ControlFlow) {
|
|
||||||
if self.should_quit {
|
|
||||||
emu::save_and_exit(cpu, control_flow);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn request_redraw(&self) {
|
|
||||||
self.window.request_redraw();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn handle_event<T>(&mut self, event: &Event<T>) {
|
|
||||||
self.platform.handle_event(event);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_time(&mut self, elapsed_seconds: f64) {
|
|
||||||
self.platform.update_time(elapsed_seconds);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn resize(&mut self, size: PhysicalSize<u32>) {
|
|
||||||
// See: https://github.com/rust-windowing/winit/issues/208
|
|
||||||
if size.width > 0 && size.height > 0 {
|
|
||||||
self.surface_config.width = size.width;
|
|
||||||
self.surface_config.height = size.height;
|
|
||||||
self.surface.configure(&self.device, &self.surface_config);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn paint(&mut self, cpu: &Cpu) {
|
|
||||||
use wgpu::{Color, SurfaceError};
|
|
||||||
|
|
||||||
let data = emu::pixel_buf(cpu);
|
|
||||||
write_to_texture(&self.queue, &self.texture, data, self.texture_size);
|
|
||||||
|
|
||||||
let output_frame = match self.surface.get_current_texture() {
|
|
||||||
Ok(frame) => frame,
|
|
||||||
Err(SurfaceError::Outdated) => return, // Occurs on minimization on Windows
|
|
||||||
Err(e) => {
|
|
||||||
eprintln!("Dropped frame with error: {}", e);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
let output_view = output_frame
|
|
||||||
.texture
|
|
||||||
.create_view(&TextureViewDescriptor::default());
|
|
||||||
|
|
||||||
// Begin to draw Egui components
|
|
||||||
self.platform.begin_frame();
|
|
||||||
self.draw_egui(cpu, &self.platform.context(), self.texture_id);
|
|
||||||
|
|
||||||
// End the UI frame. We could now handle the output and draw the UI with the backend.
|
|
||||||
let full_output = self.platform.end_frame(Some(&self.window));
|
|
||||||
let paint_jobs = self.platform.context().tessellate(full_output.shapes);
|
|
||||||
|
|
||||||
let mut encoder = create_command_encoder(&self.device);
|
|
||||||
let screen_descriptor = create_screen_descriptor(&self.window, &self.surface_config);
|
|
||||||
let tdelta = full_output.textures_delta;
|
|
||||||
// Upload all resources for the GPU.
|
|
||||||
self.render_pass
|
|
||||||
.add_textures(&self.device, &self.queue, &tdelta)
|
|
||||||
.expect("add texture ok");
|
|
||||||
self.render_pass
|
|
||||||
.update_buffers(&self.device, &self.queue, &paint_jobs, &screen_descriptor);
|
|
||||||
|
|
||||||
// Record all render passes.
|
|
||||||
self.render_pass
|
|
||||||
.execute(
|
|
||||||
&mut encoder,
|
|
||||||
&output_view,
|
|
||||||
&paint_jobs,
|
|
||||||
&screen_descriptor,
|
|
||||||
Some(Color::BLACK),
|
|
||||||
)
|
|
||||||
.expect("execute render pass");
|
|
||||||
|
|
||||||
// Submit the commands.
|
|
||||||
self.queue.submit(std::iter::once(encoder.finish()));
|
|
||||||
|
|
||||||
// Redraw egui
|
|
||||||
output_frame.present();
|
|
||||||
|
|
||||||
self.render_pass
|
|
||||||
.remove_textures(tdelta)
|
|
||||||
.expect("remove texture delta");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn draw_egui(&mut self, cpu: &Cpu, ctx: &Context, texture_id: TextureId) {
|
|
||||||
use crate::{cpu, instruction, ppu};
|
|
||||||
|
|
||||||
fn selectable_text(ui: &mut egui::Ui, mut text: &str) -> egui::Response {
|
|
||||||
ui.add(egui::TextEdit::multiline(&mut text).code_editor())
|
|
||||||
}
|
|
||||||
|
|
||||||
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
|
|
||||||
ui.menu_button("File", |ui| {
|
|
||||||
if ui.button("Quit").clicked() {
|
|
||||||
self.should_quit = true;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
egui::Window::new(&self.title).show(ctx, |ui| {
|
|
||||||
ui.image(
|
|
||||||
texture_id,
|
|
||||||
[GB_WIDTH as f32 * SCALE, GB_HEIGHT as f32 * SCALE],
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
egui::Window::new("Disassembly").show(ctx, |ui| {
|
|
||||||
selectable_text(ui, &instruction::dbg::tmp_disasm(cpu, 20));
|
|
||||||
});
|
|
||||||
|
|
||||||
egui::Window::new("Settings").show(ctx, |ui| {
|
|
||||||
egui::ComboBox::from_label("Emulation Mode")
|
|
||||||
.selected_text(format!("{:?}", self.mode))
|
|
||||||
.show_ui(ui, |ui| {
|
|
||||||
ui.selectable_value(&mut self.mode, EmuMode::Running, "Running");
|
|
||||||
ui.selectable_value(&mut self.mode, EmuMode::Stopped, "Stopped");
|
|
||||||
ui.selectable_value(&mut self.mode, EmuMode::StepFrame, "Step Frame");
|
|
||||||
ui.selectable_value(&mut self.mode, EmuMode::Step, "Step");
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
egui::Window::new("GB Info").show(ctx, |ui| {
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.vertical(|ui| {
|
|
||||||
ui.heading("CPU");
|
|
||||||
|
|
||||||
ui.monospace(format!("AF: {:#06X}", cpu::dbg::af(cpu)));
|
|
||||||
ui.monospace(format!("BC: {:#06X}", cpu::dbg::bc(cpu)));
|
|
||||||
ui.monospace(format!("DE: {:#06X}", cpu::dbg::de(cpu)));
|
|
||||||
ui.monospace(format!("HL: {:#06X}", cpu::dbg::hl(cpu)));
|
|
||||||
ui.add_space(10.0);
|
|
||||||
ui.monospace(format!("SP: {:#06X}", cpu::dbg::sp(cpu)));
|
|
||||||
ui.monospace(format!("PC: {:#06X}", cpu::dbg::pc(cpu)));
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.vertical(|ui| {
|
|
||||||
let ppu = &cpu.bus.ppu;
|
|
||||||
|
|
||||||
ui.heading("PPU");
|
|
||||||
|
|
||||||
ui.monospace(format!("LY: {}", ppu::dbg::ly(ppu)));
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.monospace(format!("SCX: {}", ppu::dbg::scx(ppu)));
|
|
||||||
ui.monospace(format!("SCY: {}", ppu::dbg::scy(ppu)));
|
|
||||||
});
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
ui.monospace(format!("WX: {}", ppu::dbg::wx(ppu)));
|
|
||||||
ui.monospace(format!("WY: {}", ppu::dbg::wy(ppu)));
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.monospace(format!(
|
|
||||||
"Mode: {:?} {}",
|
|
||||||
ppu::dbg::mode(ppu),
|
|
||||||
ppu::dbg::dot(ppu)
|
|
||||||
))
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.add_space(10.0);
|
|
||||||
|
|
||||||
let _ = ui.selectable_label(cpu::dbg::ime(cpu), "IME");
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let irq = cpu.int_request();
|
|
||||||
|
|
||||||
ui.label("IRQ:");
|
|
||||||
let _ = ui.selectable_label(irq & 0b01 == 0x01, "VBlank");
|
|
||||||
let _ = ui.selectable_label(irq >> 1 & 0x01 == 0x01, "LCD STAT");
|
|
||||||
let _ = ui.selectable_label(irq >> 2 & 0x01 == 0x01, "Timer");
|
|
||||||
let _ = ui.selectable_label(irq >> 3 & 0x01 == 0x01, "Serial");
|
|
||||||
let _ = ui.selectable_label(irq >> 4 & 0x01 == 0x01, "Joypad");
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let ie = cpu.int_enable();
|
|
||||||
|
|
||||||
// TODO: Reimplement this
|
|
||||||
// let r_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'R');
|
|
||||||
// let e_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'E');
|
|
||||||
// let q_len = ctx.fonts().glyph_width(egui::TextStyle::Body, 'Q');
|
|
||||||
|
|
||||||
ui.label("IE:");
|
|
||||||
// ui.add_space(q_len - (e_len - r_len));
|
|
||||||
let _ = ui.selectable_label(ie & 0b01 == 0x01, "VBlank");
|
|
||||||
let _ = ui.selectable_label(ie >> 1 & 0x01 == 0x01, "LCD STAT");
|
|
||||||
let _ = ui.selectable_label(ie >> 2 & 0x01 == 0x01, "Timer");
|
|
||||||
let _ = ui.selectable_label(ie >> 3 & 0x01 == 0x01, "Serial");
|
|
||||||
let _ = ui.selectable_label(ie >> 4 & 0x01 == 0x01, "Joypad");
|
|
||||||
});
|
|
||||||
|
|
||||||
ui.add_space(10.0);
|
|
||||||
|
|
||||||
ui.horizontal(|ui| {
|
|
||||||
let flags = cpu::dbg::flags(cpu);
|
|
||||||
|
|
||||||
let _ = ui.selectable_label((flags >> 7 & 0x01) == 0x01, "Zero");
|
|
||||||
let _ = ui.selectable_label((flags >> 6 & 0x01) == 0x01, "Negative");
|
|
||||||
let _ = ui.selectable_label((flags >> 5 & 0x01) == 0x01, "Half-Carry");
|
|
||||||
let _ = ui.selectable_label((flags >> 4 & 0x01) == 0x01, "Carry");
|
|
||||||
})
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
||||||
pub enum EmuMode {
|
|
||||||
Running,
|
|
||||||
StepFrame,
|
|
||||||
Stopped,
|
|
||||||
Step,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// To avoid using an [Option<KeyboardInput>] to keep track of user input from winit,
|
|
||||||
/// we can use a "default" value. However, in order for this to work the chosen "default"
|
|
||||||
/// value must be an **unused** key, so that it is ignored by the emulator.
|
|
||||||
pub fn unused_key() -> KeyboardInput {
|
|
||||||
#![allow(deprecated)]
|
|
||||||
|
|
||||||
KeyboardInput {
|
|
||||||
scancode: Default::default(),
|
|
||||||
state: ElementState::Released,
|
|
||||||
virtual_keycode: Default::default(),
|
|
||||||
modifiers: Default::default(), // this argument is deprecated
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_window<T>(event_loop: &EventLoop<T>) -> Result<Window, OsError> {
|
|
||||||
use winit::window::WindowBuilder;
|
|
||||||
|
|
||||||
WindowBuilder::new()
|
|
||||||
.with_decorations(true)
|
|
||||||
.with_resizable(true)
|
|
||||||
.with_transparent(false)
|
|
||||||
.with_title(WINDOW_TITLE)
|
|
||||||
.with_inner_size(PhysicalSize {
|
|
||||||
width: EGUI_DIMENSIONS.0 as f32,
|
|
||||||
height: EGUI_DIMENSIONS.1 as f32,
|
|
||||||
})
|
|
||||||
.build(event_loop)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn request_adapter(instance: &Instance, surface: &Surface) -> Option<Adapter> {
|
|
||||||
use wgpu::{PowerPreference, RequestAdapterOptions};
|
|
||||||
|
|
||||||
pollster::block_on(instance.request_adapter(&RequestAdapterOptions {
|
|
||||||
power_preference: PowerPreference::HighPerformance,
|
|
||||||
compatible_surface: Some(surface),
|
|
||||||
force_fallback_adapter: false, // TODO: What do I want to do with this?
|
|
||||||
}))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn request_device(adapter: &Adapter) -> Result<(Device, Queue), RequestDeviceError> {
|
|
||||||
use wgpu::{DeviceDescriptor, Features, Limits};
|
|
||||||
|
|
||||||
pollster::block_on(adapter.request_device(
|
|
||||||
&DeviceDescriptor {
|
|
||||||
features: Features::default(),
|
|
||||||
limits: Limits::default(),
|
|
||||||
label: None,
|
|
||||||
},
|
|
||||||
None,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn surface_config(window: &Window, capabilities: SurfaceCapabilities) -> SurfaceConfiguration {
|
|
||||||
use egui_wgpu_backend::wgpu::{PresentMode, TextureFormat, TextureUsages};
|
|
||||||
|
|
||||||
let size = window.inner_size();
|
|
||||||
SurfaceConfiguration {
|
|
||||||
usage: TextureUsages::RENDER_ATTACHMENT,
|
|
||||||
format: capabilities.formats[0],
|
|
||||||
width: size.width as u32,
|
|
||||||
height: size.height as u32,
|
|
||||||
present_mode: PresentMode::Fifo,
|
|
||||||
alpha_mode: capabilities.alpha_modes[0],
|
|
||||||
view_formats: vec![TextureFormat::Rgba8UnormSrgb],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn platform(window: &Window) -> Platform {
|
|
||||||
use egui::FontDefinitions;
|
|
||||||
use egui_winit_platform::PlatformDescriptor;
|
|
||||||
|
|
||||||
let size = window.inner_size();
|
|
||||||
Platform::new(PlatformDescriptor {
|
|
||||||
physical_width: size.width as u32,
|
|
||||||
physical_height: size.height as u32,
|
|
||||||
scale_factor: window.scale_factor(),
|
|
||||||
font_definitions: FontDefinitions::default(),
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn texture_size() -> Extent3d {
|
|
||||||
Extent3d {
|
|
||||||
width: GB_WIDTH as u32,
|
|
||||||
height: GB_HEIGHT as u32,
|
|
||||||
..Default::default()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_texture(device: &Device, size: Extent3d) -> Texture {
|
|
||||||
use wgpu::{TextureDescriptor, TextureDimension, TextureFormat, TextureUsages};
|
|
||||||
|
|
||||||
device.create_texture(&TextureDescriptor {
|
|
||||||
size,
|
|
||||||
mip_level_count: 1,
|
|
||||||
sample_count: 1,
|
|
||||||
dimension: TextureDimension::D2,
|
|
||||||
format: TextureFormat::Bgra8Unorm,
|
|
||||||
usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING,
|
|
||||||
label: Some("gb_pixel_buffer"),
|
|
||||||
view_formats: &[TextureFormat::Bgra8Unorm],
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn write_to_texture(
|
|
||||||
queue: &Queue,
|
|
||||||
texture: &Texture,
|
|
||||||
data: &[u8; GB_WIDTH * 4 * GB_HEIGHT],
|
|
||||||
size: Extent3d,
|
|
||||||
) {
|
|
||||||
use std::num::NonZeroU32;
|
|
||||||
use wgpu::{ImageCopyTexture, ImageDataLayout, Origin3d, TextureAspect};
|
|
||||||
|
|
||||||
queue.write_texture(
|
|
||||||
ImageCopyTexture {
|
|
||||||
texture,
|
|
||||||
mip_level: 0,
|
|
||||||
origin: Origin3d::ZERO,
|
|
||||||
aspect: TextureAspect::All,
|
|
||||||
},
|
|
||||||
data,
|
|
||||||
ImageDataLayout {
|
|
||||||
offset: 0,
|
|
||||||
bytes_per_row: NonZeroU32::new(4 * GB_WIDTH as u32),
|
|
||||||
rows_per_image: NonZeroU32::new(GB_HEIGHT as u32),
|
|
||||||
},
|
|
||||||
size,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn expose_texture_to_egui(
|
|
||||||
render_pass: &mut RenderPass,
|
|
||||||
device: &Device,
|
|
||||||
view: &TextureView,
|
|
||||||
) -> TextureId {
|
|
||||||
render_pass.egui_texture_from_wgpu_texture(device, view, FILTER_MODE)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn create_command_encoder(device: &Device) -> CommandEncoder {
|
|
||||||
use wgpu::CommandEncoderDescriptor;
|
|
||||||
|
|
||||||
device.create_command_encoder(&CommandEncoderDescriptor {
|
|
||||||
label: Some("encoder"),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn create_screen_descriptor(window: &Window, config: &SurfaceConfiguration) -> ScreenDescriptor {
|
|
||||||
ScreenDescriptor {
|
|
||||||
physical_width: config.width,
|
|
||||||
physical_height: config.height,
|
|
||||||
scale_factor: window.scale_factor() as f32,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod kbd {
|
|
||||||
use winit::event::{ElementState, KeyboardInput, VirtualKeyCode};
|
|
||||||
|
|
||||||
pub fn space_released(input: &KeyboardInput) -> bool {
|
|
||||||
let keycode = input.virtual_keycode;
|
|
||||||
matches!(input.state, ElementState::Released if keycode == Some(VirtualKeyCode::Space))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -17,11 +17,11 @@ impl Default for HighRam {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BusIo for HighRam {
|
impl BusIo for HighRam {
|
||||||
fn read_byte(&self, addr: u16) -> u8 {
|
|
||||||
self.buf[addr as usize - HIGH_RAM_START_ADDRESS]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_byte(&mut self, addr: u16, byte: u8) {
|
fn write_byte(&mut self, addr: u16, byte: u8) {
|
||||||
self.buf[addr as usize - HIGH_RAM_START_ADDRESS] = byte;
|
self.buf[addr as usize - HIGH_RAM_START_ADDRESS] = byte;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_byte(&self, addr: u16) -> u8 {
|
||||||
|
self.buf[addr as usize - HIGH_RAM_START_ADDRESS]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use self::add::{Source as AddSource, Target as AddTarget};
|
use self::add::{Source as AddSource, Target as AddTarget};
|
||||||
use self::alu::Source as AluSource;
|
use self::alu::Source as AluSource;
|
||||||
use self::jump::{JpCond, JpLoc};
|
use self::jump::{JumpCondition, JumpLocation};
|
||||||
use self::load::{Source as LDSource, Target as LDTarget};
|
use self::load::{Source as LDSource, Target as LDTarget};
|
||||||
use self::table::{
|
use self::table::{
|
||||||
alu_imm_instr, alu_reg_instr, flag_instr, group1, group2, group3, jump_cond, prefix_alu,
|
alu_imm_instr, alu_reg_instr, flag_instr, group1, group2, group3, jump_cond, prefix_alu,
|
||||||
|
@ -16,7 +16,7 @@ use crate::Cycle;
|
||||||
pub(crate) enum Instruction {
|
pub(crate) enum Instruction {
|
||||||
NOP,
|
NOP,
|
||||||
STOP,
|
STOP,
|
||||||
JR(JpCond),
|
JR(JumpCondition),
|
||||||
LD(LDTarget, LDSource),
|
LD(LDTarget, LDSource),
|
||||||
ADD(AddTarget, AddSource),
|
ADD(AddTarget, AddSource),
|
||||||
LDHL,
|
LDHL,
|
||||||
|
@ -38,13 +38,13 @@ pub(crate) enum Instruction {
|
||||||
XOR(AluSource),
|
XOR(AluSource),
|
||||||
OR(AluSource),
|
OR(AluSource),
|
||||||
CP(AluSource),
|
CP(AluSource),
|
||||||
RET(JpCond),
|
RET(JumpCondition),
|
||||||
POP(Group3RegisterPair),
|
POP(Group3RegisterPair),
|
||||||
RETI,
|
RETI,
|
||||||
JP(JpCond, JpLoc),
|
JP(JumpCondition, JumpLocation),
|
||||||
DI,
|
DI,
|
||||||
EI,
|
EI,
|
||||||
CALL(JpCond),
|
CALL(JumpCondition),
|
||||||
PUSH(Group3RegisterPair),
|
PUSH(Group3RegisterPair),
|
||||||
RST(u8),
|
RST(u8),
|
||||||
RLC(Register),
|
RLC(Register),
|
||||||
|
@ -58,7 +58,6 @@ pub(crate) enum Instruction {
|
||||||
BIT(u8, Register),
|
BIT(u8, Register),
|
||||||
RES(u8, Register),
|
RES(u8, Register),
|
||||||
SET(u8, Register),
|
SET(u8, Register),
|
||||||
Invalid,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for Instruction {
|
impl std::fmt::Debug for Instruction {
|
||||||
|
@ -110,7 +109,6 @@ impl std::fmt::Debug for Instruction {
|
||||||
BIT(b, r) => write!(f, "BIT {}, {:?}", b, r),
|
BIT(b, r) => write!(f, "BIT {}, {:?}", b, r),
|
||||||
RES(b, r) => write!(f, "RES {}, {:?}", b, r),
|
RES(b, r) => write!(f, "RES {}, {:?}", b, r),
|
||||||
SET(b, r) => write!(f, "SET {}, {:?}", b, r),
|
SET(b, r) => write!(f, "SET {}, {:?}", b, r),
|
||||||
Invalid => f.write_str("???"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -118,7 +116,7 @@ impl std::fmt::Debug for Instruction {
|
||||||
impl Instruction {
|
impl Instruction {
|
||||||
pub(crate) fn execute(cpu: &mut Cpu, instruction: Self) -> Cycle {
|
pub(crate) fn execute(cpu: &mut Cpu, instruction: Self) -> Cycle {
|
||||||
match instruction {
|
match instruction {
|
||||||
Instruction::NOP => 4,
|
Instruction::NOP => (4),
|
||||||
Instruction::LD(target, src) => match (target, src) {
|
Instruction::LD(target, src) => match (target, src) {
|
||||||
(LDTarget::IndirectImmediateWord, LDSource::SP) => {
|
(LDTarget::IndirectImmediateWord, LDSource::SP) => {
|
||||||
// LD (u16), SP | Store stack pointer in byte at 16-bit register
|
// LD (u16), SP | Store stack pointer in byte at 16-bit register
|
||||||
|
@ -295,7 +293,7 @@ impl Instruction {
|
||||||
let addr = pc.wrapping_add(byte as u16);
|
let addr = pc.wrapping_add(byte as u16);
|
||||||
|
|
||||||
match cond {
|
match cond {
|
||||||
JpCond::NotZero => {
|
JumpCondition::NotZero => {
|
||||||
if !flags.z() {
|
if !flags.z() {
|
||||||
Self::jump(cpu, addr);
|
Self::jump(cpu, addr);
|
||||||
12
|
12
|
||||||
|
@ -303,7 +301,7 @@ impl Instruction {
|
||||||
8
|
8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JpCond::Zero => {
|
JumpCondition::Zero => {
|
||||||
if flags.z() {
|
if flags.z() {
|
||||||
Self::jump(cpu, addr);
|
Self::jump(cpu, addr);
|
||||||
12
|
12
|
||||||
|
@ -311,7 +309,7 @@ impl Instruction {
|
||||||
8
|
8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JpCond::NotCarry => {
|
JumpCondition::NotCarry => {
|
||||||
if !flags.c() {
|
if !flags.c() {
|
||||||
Self::jump(cpu, addr);
|
Self::jump(cpu, addr);
|
||||||
12
|
12
|
||||||
|
@ -319,7 +317,7 @@ impl Instruction {
|
||||||
8
|
8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JpCond::Carry => {
|
JumpCondition::Carry => {
|
||||||
if flags.c() {
|
if flags.c() {
|
||||||
Self::jump(cpu, addr);
|
Self::jump(cpu, addr);
|
||||||
12
|
12
|
||||||
|
@ -327,7 +325,7 @@ impl Instruction {
|
||||||
8
|
8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JpCond::Always => {
|
JumpCondition::Always => {
|
||||||
Self::jump(cpu, addr);
|
Self::jump(cpu, addr);
|
||||||
12
|
12
|
||||||
}
|
}
|
||||||
|
@ -336,6 +334,7 @@ impl Instruction {
|
||||||
Instruction::ADD(target, src) => match (target, src) {
|
Instruction::ADD(target, src) => match (target, src) {
|
||||||
(AddTarget::HL, AddSource::Group1(pair)) => {
|
(AddTarget::HL, AddSource::Group1(pair)) => {
|
||||||
// ADD HL, r16 | Add 16-bit register to HL
|
// ADD HL, r16 | Add 16-bit register to HL
|
||||||
|
// FIXME: Memory Timings are not properly emulated for this instruction
|
||||||
use Group1RegisterPair::*;
|
use Group1RegisterPair::*;
|
||||||
let mut flags: Flags = *cpu.flags();
|
let mut flags: Flags = *cpu.flags();
|
||||||
|
|
||||||
|
@ -343,11 +342,10 @@ impl Instruction {
|
||||||
BC | DE | HL | SP => {
|
BC | DE | HL | SP => {
|
||||||
let left = cpu.register_pair(RegisterPair::HL);
|
let left = cpu.register_pair(RegisterPair::HL);
|
||||||
let right = cpu.register_pair(pair.as_register_pair());
|
let right = cpu.register_pair(pair.as_register_pair());
|
||||||
let result = Self::add_u16(left, right, &mut flags);
|
cpu.set_register_pair(
|
||||||
|
RegisterPair::HL,
|
||||||
cpu.set_register(CpuRegister::L, result as u8);
|
Self::add_u16(left, right, &mut flags),
|
||||||
cpu.bus.clock();
|
);
|
||||||
cpu.set_register(CpuRegister::H, (result >> 8) as u8);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cpu.set_flags(flags);
|
cpu.set_flags(flags);
|
||||||
|
@ -363,12 +361,12 @@ impl Instruction {
|
||||||
let (cycles, sum) = match reg {
|
let (cycles, sum) = match reg {
|
||||||
B | C | D | E | H | L | A => {
|
B | C | D | E | H | L | A => {
|
||||||
let right = cpu.register(reg.cpu_register());
|
let right = cpu.register(reg.cpu_register());
|
||||||
(4, Self::add(left, right, &mut flags))
|
((4), Self::add(left, right, &mut flags))
|
||||||
}
|
}
|
||||||
IndirectHL => {
|
IndirectHL => {
|
||||||
let addr = cpu.register_pair(RegisterPair::HL);
|
let addr = cpu.register_pair(RegisterPair::HL);
|
||||||
let right = Self::read_byte(&mut cpu.bus, addr);
|
let right = Self::read_byte(&mut cpu.bus, addr);
|
||||||
(8, Self::add(left, right, &mut flags))
|
((8), Self::add(left, right, &mut flags))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -378,15 +376,12 @@ impl Instruction {
|
||||||
}
|
}
|
||||||
(AddTarget::SP, AddSource::ImmediateSignedByte) => {
|
(AddTarget::SP, AddSource::ImmediateSignedByte) => {
|
||||||
// ADD SP, i8 | Add i8 to stack pointer
|
// ADD SP, i8 | Add i8 to stack pointer
|
||||||
|
// FIXME: Memory Timings are not properly emulated for this instruction
|
||||||
let mut flags: Flags = *cpu.flags();
|
let mut flags: Flags = *cpu.flags();
|
||||||
|
|
||||||
let left = cpu.register_pair(RegisterPair::SP);
|
let left = cpu.register_pair(RegisterPair::SP);
|
||||||
let sum = Self::add_u16_i8(left, Self::imm_byte(cpu) as i8, &mut flags);
|
let sum = Self::add_u16_i8(left, Self::imm_byte(cpu) as i8, &mut flags);
|
||||||
cpu.bus.clock(); // internal
|
|
||||||
|
|
||||||
cpu.set_register_pair(RegisterPair::SP, sum);
|
cpu.set_register_pair(RegisterPair::SP, sum);
|
||||||
cpu.bus.clock();
|
|
||||||
|
|
||||||
cpu.set_flags(flags);
|
cpu.set_flags(flags);
|
||||||
16
|
16
|
||||||
}
|
}
|
||||||
|
@ -428,6 +423,7 @@ impl Instruction {
|
||||||
AllRegisters::Group1(pair) => {
|
AllRegisters::Group1(pair) => {
|
||||||
// INC r16 | Increment 16-bit register
|
// INC r16 | Increment 16-bit register
|
||||||
// Note: No flags are set with this version of the INC instruction
|
// Note: No flags are set with this version of the INC instruction
|
||||||
|
// FIXME: Memory Timings are not properly emulated for this instruction
|
||||||
use Group1RegisterPair::*;
|
use Group1RegisterPair::*;
|
||||||
|
|
||||||
match pair {
|
match pair {
|
||||||
|
@ -435,7 +431,6 @@ impl Instruction {
|
||||||
let pair = pair.as_register_pair();
|
let pair = pair.as_register_pair();
|
||||||
let left = cpu.register_pair(pair);
|
let left = cpu.register_pair(pair);
|
||||||
cpu.set_register_pair(pair, left.wrapping_add(1));
|
cpu.set_register_pair(pair, left.wrapping_add(1));
|
||||||
cpu.bus.clock(); // internal
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
8
|
8
|
||||||
|
@ -467,6 +462,7 @@ impl Instruction {
|
||||||
}
|
}
|
||||||
AllRegisters::Group1(pair) => {
|
AllRegisters::Group1(pair) => {
|
||||||
// DEC r16 | Decrement Register Pair
|
// DEC r16 | Decrement Register Pair
|
||||||
|
// FIXME: Memory Timings are not properly emulated for this instruction
|
||||||
use Group1RegisterPair::*;
|
use Group1RegisterPair::*;
|
||||||
|
|
||||||
match pair {
|
match pair {
|
||||||
|
@ -474,7 +470,6 @@ impl Instruction {
|
||||||
let pair = pair.as_register_pair();
|
let pair = pair.as_register_pair();
|
||||||
let left = cpu.register_pair(pair);
|
let left = cpu.register_pair(pair);
|
||||||
cpu.set_register_pair(pair, left.wrapping_sub(1));
|
cpu.set_register_pair(pair, left.wrapping_sub(1));
|
||||||
cpu.bus.clock(); // internal
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
8
|
8
|
||||||
|
@ -615,13 +610,13 @@ impl Instruction {
|
||||||
B | C | D | E | H | L | A => {
|
B | C | D | E | H | L | A => {
|
||||||
let right = cpu.register(reg.cpu_register());
|
let right = cpu.register(reg.cpu_register());
|
||||||
let sum = Self::add_with_carry_bit(left, right, flags.c(), &mut flags);
|
let sum = Self::add_with_carry_bit(left, right, flags.c(), &mut flags);
|
||||||
(4, sum)
|
((4), sum)
|
||||||
}
|
}
|
||||||
IndirectHL => {
|
IndirectHL => {
|
||||||
let addr = cpu.register_pair(RegisterPair::HL);
|
let addr = cpu.register_pair(RegisterPair::HL);
|
||||||
let right = Self::read_byte(&mut cpu.bus, addr);
|
let right = Self::read_byte(&mut cpu.bus, addr);
|
||||||
let sum = Self::add_with_carry_bit(left, right, flags.c(), &mut flags);
|
let sum = Self::add_with_carry_bit(left, right, flags.c(), &mut flags);
|
||||||
(8, sum)
|
((8), sum)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cpu.set_register(CpuRegister::A, sum);
|
cpu.set_register(CpuRegister::A, sum);
|
||||||
|
@ -651,12 +646,12 @@ impl Instruction {
|
||||||
let (cycles, diff) = match reg {
|
let (cycles, diff) = match reg {
|
||||||
B | C | D | E | H | L | A => {
|
B | C | D | E | H | L | A => {
|
||||||
let right = cpu.register(reg.cpu_register());
|
let right = cpu.register(reg.cpu_register());
|
||||||
(4, Self::sub(left, right, &mut flags))
|
((4), Self::sub(left, right, &mut flags))
|
||||||
}
|
}
|
||||||
IndirectHL => {
|
IndirectHL => {
|
||||||
let addr = cpu.register_pair(RegisterPair::HL);
|
let addr = cpu.register_pair(RegisterPair::HL);
|
||||||
let right = Self::read_byte(&mut cpu.bus, addr);
|
let right = Self::read_byte(&mut cpu.bus, addr);
|
||||||
(8, Self::sub(left, right, &mut flags))
|
((8), Self::sub(left, right, &mut flags))
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cpu.set_register(CpuRegister::A, diff);
|
cpu.set_register(CpuRegister::A, diff);
|
||||||
|
@ -686,13 +681,13 @@ impl Instruction {
|
||||||
B | C | D | E | H | L | A => {
|
B | C | D | E | H | L | A => {
|
||||||
let right = cpu.register(reg.cpu_register());
|
let right = cpu.register(reg.cpu_register());
|
||||||
let diff = Self::sub_with_carry(left, right, flags.c(), &mut flags);
|
let diff = Self::sub_with_carry(left, right, flags.c(), &mut flags);
|
||||||
(4, diff)
|
((4), diff)
|
||||||
}
|
}
|
||||||
IndirectHL => {
|
IndirectHL => {
|
||||||
let addr = cpu.register_pair(RegisterPair::HL);
|
let addr = cpu.register_pair(RegisterPair::HL);
|
||||||
let right = Self::read_byte(&mut cpu.bus, addr);
|
let right = Self::read_byte(&mut cpu.bus, addr);
|
||||||
let diff = Self::sub_with_carry(left, right, flags.c(), &mut flags);
|
let diff = Self::sub_with_carry(left, right, flags.c(), &mut flags);
|
||||||
(8, diff)
|
((8), diff)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cpu.set_register(CpuRegister::A, diff);
|
cpu.set_register(CpuRegister::A, diff);
|
||||||
|
@ -722,7 +717,7 @@ impl Instruction {
|
||||||
IndirectHL => {
|
IndirectHL => {
|
||||||
let addr = cpu.register_pair(RegisterPair::HL);
|
let addr = cpu.register_pair(RegisterPair::HL);
|
||||||
let right = Self::read_byte(&mut cpu.bus, addr);
|
let right = Self::read_byte(&mut cpu.bus, addr);
|
||||||
(8, left & right)
|
((8), left & right)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cpu.set_register(CpuRegister::A, acc);
|
cpu.set_register(CpuRegister::A, acc);
|
||||||
|
@ -748,7 +743,7 @@ impl Instruction {
|
||||||
IndirectHL => {
|
IndirectHL => {
|
||||||
let addr = cpu.register_pair(RegisterPair::HL);
|
let addr = cpu.register_pair(RegisterPair::HL);
|
||||||
let right = Self::read_byte(&mut cpu.bus, addr);
|
let right = Self::read_byte(&mut cpu.bus, addr);
|
||||||
(8, left ^ right)
|
((8), left ^ right)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cpu.set_register(CpuRegister::A, acc);
|
cpu.set_register(CpuRegister::A, acc);
|
||||||
|
@ -774,7 +769,7 @@ impl Instruction {
|
||||||
IndirectHL => {
|
IndirectHL => {
|
||||||
let addr = cpu.register_pair(RegisterPair::HL);
|
let addr = cpu.register_pair(RegisterPair::HL);
|
||||||
let right = Self::read_byte(&mut cpu.bus, addr);
|
let right = Self::read_byte(&mut cpu.bus, addr);
|
||||||
(8, left | right)
|
((8), left | right)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cpu.set_register(CpuRegister::A, acc);
|
cpu.set_register(CpuRegister::A, acc);
|
||||||
|
@ -839,7 +834,7 @@ impl Instruction {
|
||||||
let flags: Flags = *cpu.flags();
|
let flags: Flags = *cpu.flags();
|
||||||
|
|
||||||
match cond {
|
match cond {
|
||||||
JpCond::NotZero => {
|
JumpCondition::NotZero => {
|
||||||
cpu.bus.clock(); // internal branch decision
|
cpu.bus.clock(); // internal branch decision
|
||||||
|
|
||||||
if !flags.z() {
|
if !flags.z() {
|
||||||
|
@ -850,7 +845,7 @@ impl Instruction {
|
||||||
8
|
8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JpCond::Zero => {
|
JumpCondition::Zero => {
|
||||||
cpu.bus.clock(); // internal branch decision
|
cpu.bus.clock(); // internal branch decision
|
||||||
|
|
||||||
if flags.z() {
|
if flags.z() {
|
||||||
|
@ -861,7 +856,7 @@ impl Instruction {
|
||||||
8
|
8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JpCond::NotCarry => {
|
JumpCondition::NotCarry => {
|
||||||
cpu.bus.clock(); // internal branch decision
|
cpu.bus.clock(); // internal branch decision
|
||||||
|
|
||||||
if !flags.c() {
|
if !flags.c() {
|
||||||
|
@ -872,7 +867,7 @@ impl Instruction {
|
||||||
8
|
8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JpCond::Carry => {
|
JumpCondition::Carry => {
|
||||||
cpu.bus.clock(); // internal branch decision
|
cpu.bus.clock(); // internal branch decision
|
||||||
|
|
||||||
if flags.c() {
|
if flags.c() {
|
||||||
|
@ -883,7 +878,7 @@ impl Instruction {
|
||||||
8
|
8
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JpCond::Always => {
|
JumpCondition::Always => {
|
||||||
let addr = Self::pop(cpu);
|
let addr = Self::pop(cpu);
|
||||||
Self::jump(cpu, addr);
|
Self::jump(cpu, addr);
|
||||||
16
|
16
|
||||||
|
@ -910,13 +905,13 @@ impl Instruction {
|
||||||
16
|
16
|
||||||
}
|
}
|
||||||
Instruction::JP(cond, location) => match location {
|
Instruction::JP(cond, location) => match location {
|
||||||
JpLoc::HL => {
|
JumpLocation::HL => {
|
||||||
// JP HL | Store HL in program counter
|
// JP HL | Store HL in program counter
|
||||||
let right = cpu.register_pair(RegisterPair::HL);
|
let right = cpu.register_pair(RegisterPair::HL);
|
||||||
cpu.set_register_pair(RegisterPair::PC, right);
|
cpu.set_register_pair(RegisterPair::PC, right);
|
||||||
4
|
4
|
||||||
}
|
}
|
||||||
JpLoc::ImmediateWord => {
|
JumpLocation::ImmediateWord => {
|
||||||
// JP cond u16 | Store u16 in program counter if condition is true
|
// JP cond u16 | Store u16 in program counter if condition is true
|
||||||
// JP u16 | Store u16 in program counter
|
// JP u16 | Store u16 in program counter
|
||||||
let flags: Flags = *cpu.flags();
|
let flags: Flags = *cpu.flags();
|
||||||
|
@ -924,7 +919,7 @@ impl Instruction {
|
||||||
let addr = Self::imm_word(cpu);
|
let addr = Self::imm_word(cpu);
|
||||||
|
|
||||||
match cond {
|
match cond {
|
||||||
JpCond::NotZero => {
|
JumpCondition::NotZero => {
|
||||||
if !flags.z() {
|
if !flags.z() {
|
||||||
Self::jump(cpu, addr);
|
Self::jump(cpu, addr);
|
||||||
16
|
16
|
||||||
|
@ -932,7 +927,7 @@ impl Instruction {
|
||||||
12
|
12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JpCond::Zero => {
|
JumpCondition::Zero => {
|
||||||
if flags.z() {
|
if flags.z() {
|
||||||
Self::jump(cpu, addr);
|
Self::jump(cpu, addr);
|
||||||
16
|
16
|
||||||
|
@ -940,7 +935,7 @@ impl Instruction {
|
||||||
12
|
12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JpCond::NotCarry => {
|
JumpCondition::NotCarry => {
|
||||||
if !flags.c() {
|
if !flags.c() {
|
||||||
Self::jump(cpu, addr);
|
Self::jump(cpu, addr);
|
||||||
16
|
16
|
||||||
|
@ -948,7 +943,7 @@ impl Instruction {
|
||||||
12
|
12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JpCond::Carry => {
|
JumpCondition::Carry => {
|
||||||
if flags.c() {
|
if flags.c() {
|
||||||
Self::jump(cpu, addr);
|
Self::jump(cpu, addr);
|
||||||
16
|
16
|
||||||
|
@ -956,7 +951,7 @@ impl Instruction {
|
||||||
12
|
12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JpCond::Always => {
|
JumpCondition::Always => {
|
||||||
Self::jump(cpu, addr);
|
Self::jump(cpu, addr);
|
||||||
16
|
16
|
||||||
}
|
}
|
||||||
|
@ -982,7 +977,7 @@ impl Instruction {
|
||||||
let return_addr = cpu.register_pair(RegisterPair::PC);
|
let return_addr = cpu.register_pair(RegisterPair::PC);
|
||||||
|
|
||||||
match cond {
|
match cond {
|
||||||
JpCond::NotZero => {
|
JumpCondition::NotZero => {
|
||||||
if !flags.z() {
|
if !flags.z() {
|
||||||
cpu.bus.clock(); // internal branch decision
|
cpu.bus.clock(); // internal branch decision
|
||||||
Self::push(cpu, return_addr);
|
Self::push(cpu, return_addr);
|
||||||
|
@ -992,7 +987,7 @@ impl Instruction {
|
||||||
12
|
12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JpCond::Zero => {
|
JumpCondition::Zero => {
|
||||||
if flags.z() {
|
if flags.z() {
|
||||||
cpu.bus.clock(); // internal branch decision
|
cpu.bus.clock(); // internal branch decision
|
||||||
Self::push(cpu, return_addr);
|
Self::push(cpu, return_addr);
|
||||||
|
@ -1002,7 +997,7 @@ impl Instruction {
|
||||||
12
|
12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JpCond::NotCarry => {
|
JumpCondition::NotCarry => {
|
||||||
if !flags.c() {
|
if !flags.c() {
|
||||||
cpu.bus.clock(); // internal branch decision
|
cpu.bus.clock(); // internal branch decision
|
||||||
Self::push(cpu, return_addr);
|
Self::push(cpu, return_addr);
|
||||||
|
@ -1012,7 +1007,7 @@ impl Instruction {
|
||||||
12
|
12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JpCond::Carry => {
|
JumpCondition::Carry => {
|
||||||
if flags.c() {
|
if flags.c() {
|
||||||
cpu.bus.clock(); // internal branch decision
|
cpu.bus.clock(); // internal branch decision
|
||||||
Self::push(cpu, return_addr);
|
Self::push(cpu, return_addr);
|
||||||
|
@ -1022,7 +1017,7 @@ impl Instruction {
|
||||||
12
|
12
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
JpCond::Always => {
|
JumpCondition::Always => {
|
||||||
cpu.bus.clock(); // internal branch decision
|
cpu.bus.clock(); // internal branch decision
|
||||||
Self::push(cpu, return_addr);
|
Self::push(cpu, return_addr);
|
||||||
cpu.set_register_pair(RegisterPair::PC, addr);
|
cpu.set_register_pair(RegisterPair::PC, addr);
|
||||||
|
@ -1058,14 +1053,14 @@ impl Instruction {
|
||||||
let byte = cpu.register(reg);
|
let byte = cpu.register(reg);
|
||||||
let rotated = byte.rotate_left(1);
|
let rotated = byte.rotate_left(1);
|
||||||
cpu.set_register(reg, rotated);
|
cpu.set_register(reg, rotated);
|
||||||
(8, byte >> 7, rotated)
|
((8), byte >> 7, rotated)
|
||||||
}
|
}
|
||||||
IndirectHL => {
|
IndirectHL => {
|
||||||
let addr = cpu.register_pair(RegisterPair::HL);
|
let addr = cpu.register_pair(RegisterPair::HL);
|
||||||
let byte = Self::read_byte(&mut cpu.bus, addr);
|
let byte = Self::read_byte(&mut cpu.bus, addr);
|
||||||
let rotated = byte.rotate_left(1);
|
let rotated = byte.rotate_left(1);
|
||||||
Self::write_byte(&mut cpu.bus, addr, rotated);
|
Self::write_byte(&mut cpu.bus, addr, rotated);
|
||||||
(16, byte >> 7, rotated)
|
((16), byte >> 7, rotated)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cpu.update_flags(rotated == 0, false, false, most_sgfnt == 0x01);
|
cpu.update_flags(rotated == 0, false, false, most_sgfnt == 0x01);
|
||||||
|
@ -1081,14 +1076,14 @@ impl Instruction {
|
||||||
let byte = cpu.register(reg);
|
let byte = cpu.register(reg);
|
||||||
let rotated = byte.rotate_right(1);
|
let rotated = byte.rotate_right(1);
|
||||||
cpu.set_register(reg, rotated);
|
cpu.set_register(reg, rotated);
|
||||||
(8, byte & 0x01, rotated)
|
((8), byte & 0x01, rotated)
|
||||||
}
|
}
|
||||||
IndirectHL => {
|
IndirectHL => {
|
||||||
let addr = cpu.register_pair(RegisterPair::HL);
|
let addr = cpu.register_pair(RegisterPair::HL);
|
||||||
let byte = Self::read_byte(&mut cpu.bus, addr);
|
let byte = Self::read_byte(&mut cpu.bus, addr);
|
||||||
let rotated = byte.rotate_right(1);
|
let rotated = byte.rotate_right(1);
|
||||||
Self::write_byte(&mut cpu.bus, addr, rotated);
|
Self::write_byte(&mut cpu.bus, addr, rotated);
|
||||||
(16, byte & 0x01, rotated)
|
((16), byte & 0x01, rotated)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cpu.update_flags(rotated == 0, false, false, least_sgfnt == 0x01);
|
cpu.update_flags(rotated == 0, false, false, least_sgfnt == 0x01);
|
||||||
|
@ -1106,14 +1101,14 @@ impl Instruction {
|
||||||
let byte = cpu.register(reg);
|
let byte = cpu.register(reg);
|
||||||
let (rotated, carry) = Self::rl_thru_carry(byte, flags.c());
|
let (rotated, carry) = Self::rl_thru_carry(byte, flags.c());
|
||||||
cpu.set_register(reg, rotated);
|
cpu.set_register(reg, rotated);
|
||||||
(8, rotated, carry)
|
((8), rotated, carry)
|
||||||
}
|
}
|
||||||
IndirectHL => {
|
IndirectHL => {
|
||||||
let addr = cpu.register_pair(RegisterPair::HL);
|
let addr = cpu.register_pair(RegisterPair::HL);
|
||||||
let byte = Self::read_byte(&mut cpu.bus, addr);
|
let byte = Self::read_byte(&mut cpu.bus, addr);
|
||||||
let (rotated, carry) = Self::rl_thru_carry(byte, flags.c());
|
let (rotated, carry) = Self::rl_thru_carry(byte, flags.c());
|
||||||
Self::write_byte(&mut cpu.bus, addr, rotated);
|
Self::write_byte(&mut cpu.bus, addr, rotated);
|
||||||
(16, rotated, carry)
|
((16), rotated, carry)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cpu.update_flags(rotated == 0, false, false, carry);
|
cpu.update_flags(rotated == 0, false, false, carry);
|
||||||
|
@ -1131,14 +1126,14 @@ impl Instruction {
|
||||||
let byte = cpu.register(reg);
|
let byte = cpu.register(reg);
|
||||||
let (rotated, carry) = Self::rr_thru_carry(byte, flags.c());
|
let (rotated, carry) = Self::rr_thru_carry(byte, flags.c());
|
||||||
cpu.set_register(reg, rotated);
|
cpu.set_register(reg, rotated);
|
||||||
(8, rotated, carry)
|
((8), rotated, carry)
|
||||||
}
|
}
|
||||||
IndirectHL => {
|
IndirectHL => {
|
||||||
let addr = cpu.register_pair(RegisterPair::HL);
|
let addr = cpu.register_pair(RegisterPair::HL);
|
||||||
let byte = Self::read_byte(&mut cpu.bus, addr);
|
let byte = Self::read_byte(&mut cpu.bus, addr);
|
||||||
let (rotated, carry) = Self::rr_thru_carry(byte, flags.c());
|
let (rotated, carry) = Self::rr_thru_carry(byte, flags.c());
|
||||||
Self::write_byte(&mut cpu.bus, addr, rotated);
|
Self::write_byte(&mut cpu.bus, addr, rotated);
|
||||||
(16, rotated, carry)
|
((16), rotated, carry)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cpu.update_flags(rotated == 0, false, false, carry);
|
cpu.update_flags(rotated == 0, false, false, carry);
|
||||||
|
@ -1154,14 +1149,14 @@ impl Instruction {
|
||||||
let byte = cpu.register(reg);
|
let byte = cpu.register(reg);
|
||||||
let shifted = byte << 1;
|
let shifted = byte << 1;
|
||||||
cpu.set_register(reg, shifted);
|
cpu.set_register(reg, shifted);
|
||||||
(8, (byte >> 7) & 0x01, shifted)
|
((8), (byte >> 7) & 0x01, shifted)
|
||||||
}
|
}
|
||||||
IndirectHL => {
|
IndirectHL => {
|
||||||
let addr = cpu.register_pair(RegisterPair::HL);
|
let addr = cpu.register_pair(RegisterPair::HL);
|
||||||
let byte = Self::read_byte(&mut cpu.bus, addr);
|
let byte = Self::read_byte(&mut cpu.bus, addr);
|
||||||
let shifted = byte << 1;
|
let shifted = byte << 1;
|
||||||
Self::write_byte(&mut cpu.bus, addr, shifted);
|
Self::write_byte(&mut cpu.bus, addr, shifted);
|
||||||
(16, (byte >> 7) & 0x01, shifted)
|
((16), (byte >> 7) & 0x01, shifted)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cpu.update_flags(shifted == 0, false, false, most_sgfnt == 0x01);
|
cpu.update_flags(shifted == 0, false, false, most_sgfnt == 0x01);
|
||||||
|
@ -1177,14 +1172,14 @@ impl Instruction {
|
||||||
let byte = cpu.register(reg);
|
let byte = cpu.register(reg);
|
||||||
let shifted = ((byte >> 7) & 0x01) << 7 | byte >> 1;
|
let shifted = ((byte >> 7) & 0x01) << 7 | byte >> 1;
|
||||||
cpu.set_register(reg, shifted);
|
cpu.set_register(reg, shifted);
|
||||||
(8, byte & 0x01, shifted)
|
((8), byte & 0x01, shifted)
|
||||||
}
|
}
|
||||||
IndirectHL => {
|
IndirectHL => {
|
||||||
let addr = cpu.register_pair(RegisterPair::HL);
|
let addr = cpu.register_pair(RegisterPair::HL);
|
||||||
let byte = Self::read_byte(&mut cpu.bus, addr);
|
let byte = Self::read_byte(&mut cpu.bus, addr);
|
||||||
let shifted = ((byte >> 7) & 0x01) << 7 | byte >> 1;
|
let shifted = ((byte >> 7) & 0x01) << 7 | byte >> 1;
|
||||||
Self::write_byte(&mut cpu.bus, addr, shifted);
|
Self::write_byte(&mut cpu.bus, addr, shifted);
|
||||||
(16, byte & 0x01, shifted)
|
((16), byte & 0x01, shifted)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cpu.update_flags(shifted == 0, false, false, least_sgfnt == 0x01);
|
cpu.update_flags(shifted == 0, false, false, least_sgfnt == 0x01);
|
||||||
|
@ -1199,14 +1194,14 @@ impl Instruction {
|
||||||
let reg = reg.cpu_register();
|
let reg = reg.cpu_register();
|
||||||
let swapped = Self::swap_bits(cpu.register(reg));
|
let swapped = Self::swap_bits(cpu.register(reg));
|
||||||
cpu.set_register(reg, swapped);
|
cpu.set_register(reg, swapped);
|
||||||
(8, swapped)
|
((8), swapped)
|
||||||
}
|
}
|
||||||
|
|
||||||
IndirectHL => {
|
IndirectHL => {
|
||||||
let addr = cpu.register_pair(RegisterPair::HL);
|
let addr = cpu.register_pair(RegisterPair::HL);
|
||||||
let swapped = Self::swap_bits(Self::read_byte(&mut cpu.bus, addr));
|
let swapped = Self::swap_bits(Self::read_byte(&mut cpu.bus, addr));
|
||||||
Self::write_byte(&mut cpu.bus, addr, swapped);
|
Self::write_byte(&mut cpu.bus, addr, swapped);
|
||||||
(16, swapped)
|
((16), swapped)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cpu.update_flags(swapped == 0, false, false, false);
|
cpu.update_flags(swapped == 0, false, false, false);
|
||||||
|
@ -1222,14 +1217,14 @@ impl Instruction {
|
||||||
let byte = cpu.register(reg);
|
let byte = cpu.register(reg);
|
||||||
let shifted = byte >> 1;
|
let shifted = byte >> 1;
|
||||||
cpu.set_register(reg, shifted);
|
cpu.set_register(reg, shifted);
|
||||||
(8, byte & 0x01, shifted)
|
((8), byte & 0x01, shifted)
|
||||||
}
|
}
|
||||||
IndirectHL => {
|
IndirectHL => {
|
||||||
let addr = cpu.register_pair(RegisterPair::HL);
|
let addr = cpu.register_pair(RegisterPair::HL);
|
||||||
let byte = Self::read_byte(&mut cpu.bus, addr);
|
let byte = Self::read_byte(&mut cpu.bus, addr);
|
||||||
let shifted = byte >> 1;
|
let shifted = byte >> 1;
|
||||||
Self::write_byte(&mut cpu.bus, addr, shifted);
|
Self::write_byte(&mut cpu.bus, addr, shifted);
|
||||||
(16, byte & 0x01, shifted)
|
((16), byte & 0x01, shifted)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
cpu.update_flags(shift_reg == 0, false, false, least_sgfnt == 0x01);
|
cpu.update_flags(shift_reg == 0, false, false, least_sgfnt == 0x01);
|
||||||
|
@ -1244,12 +1239,12 @@ impl Instruction {
|
||||||
B | C | D | E | H | L | A => {
|
B | C | D | E | H | L | A => {
|
||||||
let reg = reg.cpu_register();
|
let reg = reg.cpu_register();
|
||||||
let byte = cpu.register(reg);
|
let byte = cpu.register(reg);
|
||||||
(8, ((byte >> bit) & 0x01) == 0x01)
|
((8), ((byte >> bit) & 0x01) == 0x01)
|
||||||
}
|
}
|
||||||
IndirectHL => {
|
IndirectHL => {
|
||||||
let addr = cpu.register_pair(RegisterPair::HL);
|
let addr = cpu.register_pair(RegisterPair::HL);
|
||||||
let byte = Self::read_byte(&mut cpu.bus, addr);
|
let byte = Self::read_byte(&mut cpu.bus, addr);
|
||||||
(12, ((byte >> bit) & 0x01) == 0x01)
|
((12), ((byte >> bit) & 0x01) == 0x01)
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
flags.set_z(!is_set);
|
flags.set_z(!is_set);
|
||||||
|
@ -1296,7 +1291,6 @@ impl Instruction {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Instruction::Invalid => panic!("Attempted to execute invalid instruction"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1560,7 +1554,7 @@ impl Instruction {
|
||||||
// STOP
|
// STOP
|
||||||
0o020 => STOP,
|
0o020 => STOP,
|
||||||
// JR i8
|
// JR i8
|
||||||
0o030 => JR(JpCond::Always),
|
0o030 => JR(JumpCondition::Always),
|
||||||
// JR cond i8
|
// JR cond i8
|
||||||
0o040 | 0o050 | 0o060 | 0o070 => JR(jump_cond((byte >> 3) & 0x03)),
|
0o040 | 0o050 | 0o060 | 0o070 => JR(jump_cond((byte >> 3) & 0x03)),
|
||||||
// LD r16, u16
|
// LD r16, u16
|
||||||
|
@ -1625,16 +1619,16 @@ impl Instruction {
|
||||||
// POP r16
|
// POP r16
|
||||||
0o301 | 0o321 | 0o341 | 0o361 => POP(group3((byte >> 4) & 0x03)),
|
0o301 | 0o321 | 0o341 | 0o361 => POP(group3((byte >> 4) & 0x03)),
|
||||||
// RET
|
// RET
|
||||||
0o311 => RET(JpCond::Always),
|
0o311 => RET(JumpCondition::Always),
|
||||||
// RETI
|
// RETI
|
||||||
0o331 => RETI,
|
0o331 => RETI,
|
||||||
// JP HL
|
// JP HL
|
||||||
0o351 => JP(JpCond::Always, JpLoc::HL),
|
0o351 => JP(JumpCondition::Always, JumpLocation::HL),
|
||||||
// LD SP, HL
|
// LD SP, HL
|
||||||
0o371 => LD(LDTarget::SP, LDSource::HL),
|
0o371 => LD(LDTarget::SP, LDSource::HL),
|
||||||
// JP cond u16
|
// JP cond u16
|
||||||
0o302 | 0o312 | 0o322 | 0o332 => {
|
0o302 | 0o312 | 0o322 | 0o332 => {
|
||||||
JP(jump_cond((byte >> 3) & 0x03), JpLoc::ImmediateWord)
|
JP(jump_cond((byte >> 3) & 0x03), JumpLocation::ImmediateWord)
|
||||||
}
|
}
|
||||||
// LD (0xFF00 + C), A
|
// LD (0xFF00 + C), A
|
||||||
0o342 => LD(LDTarget::IoWithC, LDSource::A),
|
0o342 => LD(LDTarget::IoWithC, LDSource::A),
|
||||||
|
@ -1645,7 +1639,9 @@ impl Instruction {
|
||||||
// LD A, (u16)
|
// LD A, (u16)
|
||||||
0o372 => LD(LDTarget::A, LDSource::IndirectImmediateWord),
|
0o372 => LD(LDTarget::A, LDSource::IndirectImmediateWord),
|
||||||
// JP u16
|
// JP u16
|
||||||
0o303 => JP(JpCond::Always, JpLoc::ImmediateWord),
|
0o303 => JP(JumpCondition::Always, JumpLocation::ImmediateWord),
|
||||||
|
// 0xCB Prefix
|
||||||
|
0o313 => unreachable!("{:#04X} should be handled by the prefixed decoder", byte),
|
||||||
// DI
|
// DI
|
||||||
0o363 => DI,
|
0o363 => DI,
|
||||||
// EI
|
// EI
|
||||||
|
@ -1654,14 +1650,12 @@ impl Instruction {
|
||||||
0o304 | 0o314 | 0o324 | 0o334 => CALL(jump_cond((byte >> 3) & 0x03)),
|
0o304 | 0o314 | 0o324 | 0o334 => CALL(jump_cond((byte >> 3) & 0x03)),
|
||||||
// PUSH r16
|
// PUSH r16
|
||||||
0o305 | 0o325 | 0o345 | 0o365 => PUSH(group3((byte >> 4) & 0x03)),
|
0o305 | 0o325 | 0o345 | 0o365 => PUSH(group3((byte >> 4) & 0x03)),
|
||||||
// CALL u16
|
0o315 => CALL(JumpCondition::Always),
|
||||||
0o315 => CALL(JpCond::Always),
|
|
||||||
// ADD, ADC, SUB, SBC, AND, XOR, OR, and CP
|
|
||||||
0o306 | 0o316 | 0o326 | 0o336 | 0o346 | 0o356 | 0o366 | 0o376 => {
|
0o306 | 0o316 | 0o326 | 0o336 | 0o346 | 0o356 | 0o366 | 0o376 => {
|
||||||
alu_imm_instr((byte >> 3) & 0x07)
|
alu_imm_instr((byte >> 3) & 0x07)
|
||||||
}
|
}
|
||||||
0o307 | 0o317 | 0o327 | 0o337 | 0o347 | 0o357 | 0o367 | 0o377 => RST(byte & 0b00111000),
|
0o307 | 0o317 | 0o327 | 0o337 | 0o347 | 0o357 | 0o367 | 0o377 => RST(byte & 0b00111000),
|
||||||
_ => Invalid, // 0xCB is 0o313
|
_ => panic!("{:#04X} is an illegal opcode", byte),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1684,7 +1678,7 @@ impl Instruction {
|
||||||
mod jump {
|
mod jump {
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub(crate) enum JpCond {
|
pub(crate) enum JumpCondition {
|
||||||
Always,
|
Always,
|
||||||
NotZero,
|
NotZero,
|
||||||
Zero,
|
Zero,
|
||||||
|
@ -1692,9 +1686,9 @@ mod jump {
|
||||||
Carry,
|
Carry,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for JpCond {
|
impl std::fmt::Debug for JumpCondition {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
use JpCond::*;
|
use JumpCondition::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Always => f.write_str(""),
|
Always => f.write_str(""),
|
||||||
|
@ -1707,14 +1701,14 @@ mod jump {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
pub(crate) enum JpLoc {
|
pub(crate) enum JumpLocation {
|
||||||
HL,
|
HL,
|
||||||
ImmediateWord,
|
ImmediateWord,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl std::fmt::Debug for JpLoc {
|
impl std::fmt::Debug for JumpLocation {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
use JpLoc::*;
|
use JumpLocation::*;
|
||||||
|
|
||||||
match *self {
|
match *self {
|
||||||
HL => f.write_str("HL"),
|
HL => f.write_str("HL"),
|
||||||
|
@ -1874,7 +1868,7 @@ mod load {
|
||||||
mod table {
|
mod table {
|
||||||
use super::add::{Source as AddSource, Target as AddTarget};
|
use super::add::{Source as AddSource, Target as AddTarget};
|
||||||
use super::alu::Source as AluSource;
|
use super::alu::Source as AluSource;
|
||||||
use super::{Instruction, JpCond};
|
use super::{Instruction, JumpCondition};
|
||||||
use crate::cpu::{Register as CpuRegister, RegisterPair};
|
use crate::cpu::{Register as CpuRegister, RegisterPair};
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
|
@ -1899,7 +1893,7 @@ mod table {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Group1RegisterPair {
|
impl Group1RegisterPair {
|
||||||
pub(crate) fn as_register_pair(&self) -> RegisterPair {
|
pub fn as_register_pair(&self) -> RegisterPair {
|
||||||
use Group1RegisterPair::*;
|
use Group1RegisterPair::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
|
@ -1933,7 +1927,7 @@ mod table {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Group2RegisterPair {
|
impl Group2RegisterPair {
|
||||||
pub(crate) fn as_register_pair(&self) -> RegisterPair {
|
pub fn as_register_pair(&self) -> RegisterPair {
|
||||||
use Group2RegisterPair::*;
|
use Group2RegisterPair::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
|
@ -1967,7 +1961,7 @@ mod table {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Group3RegisterPair {
|
impl Group3RegisterPair {
|
||||||
pub(crate) fn as_register_pair(&self) -> RegisterPair {
|
pub fn as_register_pair(&self) -> RegisterPair {
|
||||||
use Group3RegisterPair::*;
|
use Group3RegisterPair::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
|
@ -2009,7 +2003,7 @@ mod table {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Register {
|
impl Register {
|
||||||
pub(crate) fn cpu_register(&self) -> CpuRegister {
|
pub fn cpu_register(&self) -> CpuRegister {
|
||||||
use Register::*;
|
use Register::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
|
@ -2073,12 +2067,12 @@ mod table {
|
||||||
0b101 => L,
|
0b101 => L,
|
||||||
0b110 => IndirectHL,
|
0b110 => IndirectHL,
|
||||||
0b111 => A,
|
0b111 => A,
|
||||||
_ => unreachable!("{:#04X} is not a valid Register", code),
|
_ => unreachable!("{:#04X} is not a valid Register"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn jump_cond(code: u8) -> JpCond {
|
pub(crate) fn jump_cond(code: u8) -> JumpCondition {
|
||||||
use JpCond::*;
|
use JumpCondition::*;
|
||||||
|
|
||||||
match code {
|
match code {
|
||||||
0b00 => NotZero,
|
0b00 => NotZero,
|
||||||
|
@ -2153,268 +2147,3 @@ mod table {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) mod dbg {
|
|
||||||
use std::borrow::Cow;
|
|
||||||
|
|
||||||
use super::add::{Source as AddSource, Target as AddTarget};
|
|
||||||
use super::alu::Source as AluSource;
|
|
||||||
use super::jump::{JpCond, JpLoc};
|
|
||||||
use super::load::{Source as LDSource, Target as LDTarget};
|
|
||||||
use super::{AllRegisters, BusIo, Cpu, Instruction, RegisterPair};
|
|
||||||
|
|
||||||
pub(crate) fn tmp_disasm(cpu: &Cpu, limit: u8) -> String {
|
|
||||||
let mut sm83_asm = String::new();
|
|
||||||
let mut pc = cpu.register_pair(RegisterPair::PC);
|
|
||||||
|
|
||||||
for _ in 0..limit {
|
|
||||||
let opcode = cpu.read_byte(pc);
|
|
||||||
pc += 1;
|
|
||||||
|
|
||||||
let maybe_instr = if opcode == 0xCB {
|
|
||||||
let opcode = cpu.read_byte(pc);
|
|
||||||
pc += 1;
|
|
||||||
|
|
||||||
Instruction::prefixed(opcode)
|
|
||||||
} else {
|
|
||||||
Instruction::unprefixed(opcode)
|
|
||||||
};
|
|
||||||
|
|
||||||
match maybe_instr {
|
|
||||||
Instruction::Invalid => {}
|
|
||||||
instr => {
|
|
||||||
let asm = format!("{:04X} {:?}\n", pc - 1, instr);
|
|
||||||
sm83_asm.push_str(&asm);
|
|
||||||
pc += delta::pc_inc_count(instr)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
sm83_asm
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn new_disasm(cpu: &Cpu, limit: u8) -> String {
|
|
||||||
let mut assembly = String::new();
|
|
||||||
let mut pc = cpu.register_pair(RegisterPair::PC);
|
|
||||||
|
|
||||||
for _ in 0..limit {
|
|
||||||
let opcode = cpu.read_byte(pc);
|
|
||||||
pc += 1;
|
|
||||||
|
|
||||||
let maybe_instr = if opcode == 0xCB {
|
|
||||||
let opcode = cpu.read_byte(pc);
|
|
||||||
pc += 1;
|
|
||||||
|
|
||||||
Instruction::prefixed(opcode)
|
|
||||||
} else {
|
|
||||||
Instruction::unprefixed(opcode)
|
|
||||||
};
|
|
||||||
|
|
||||||
match maybe_instr {
|
|
||||||
Instruction::Invalid => {}
|
|
||||||
instr => {
|
|
||||||
let output = format!("${:04X} {}\n", pc - 1, disasm(cpu, pc, instr));
|
|
||||||
assembly.push_str(&output);
|
|
||||||
pc += delta::pc_inc_count(instr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
assembly
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: It might be better if I pass in a mutable writer instead of usnig a String
|
|
||||||
fn disasm(cpu: &Cpu, pc: u16, instr: Instruction) -> Cow<str> {
|
|
||||||
use Instruction::*;
|
|
||||||
|
|
||||||
let imm_byte = cpu.read_byte(pc + 1);
|
|
||||||
let imm_word = (cpu.read_byte(pc + 2) as u16) << 8 | imm_byte as u16;
|
|
||||||
|
|
||||||
match instr {
|
|
||||||
// Unprefixed Instructions
|
|
||||||
NOP => "NOP".into(),
|
|
||||||
LD(LDTarget::IndirectImmediateWord, LDSource::SP) => {
|
|
||||||
format!("LD ({:#06X}), SP", imm_word).into()
|
|
||||||
}
|
|
||||||
STOP => "STOP".into(),
|
|
||||||
JR(JpCond::Always) => format!("JR {}", imm_byte as i8).into(),
|
|
||||||
JR(cond) => format!("JR {:?} {}", cond, imm_byte as i8).into(),
|
|
||||||
LD(LDTarget::Group1(rp), LDSource::ImmediateWord) => {
|
|
||||||
format!("LD {:?} {:#06X}", rp, imm_word).into()
|
|
||||||
}
|
|
||||||
ADD(AddTarget::HL, AddSource::Group1(rp)) => format!("ADD HL, {:?}", rp).into(),
|
|
||||||
LD(LDTarget::IndirectGroup2(rp), LDSource::A) => format!("LD ({:?}), A", rp).into(),
|
|
||||||
LD(LDTarget::A, LDSource::IndirectGroup2(rp)) => format!("LD A, ({:?})", rp).into(),
|
|
||||||
INC(AllRegisters::Group1(rp)) => format!("INC {:?}", rp).into(),
|
|
||||||
DEC(AllRegisters::Group1(rp)) => format!("DEC {:?}", rp).into(),
|
|
||||||
INC(AllRegisters::Register(reg)) => format!("INC {:?}", reg).into(),
|
|
||||||
DEC(AllRegisters::Register(reg)) => format!("DEC {:?}", reg).into(),
|
|
||||||
LD(LDTarget::Register(reg), LDSource::ImmediateByte) => {
|
|
||||||
format!("LD {:?}, {:#04X}", reg, imm_byte).into()
|
|
||||||
}
|
|
||||||
RLCA => "RLCA".into(),
|
|
||||||
RRCA => "RRCA".into(),
|
|
||||||
RLA => "RLA".into(),
|
|
||||||
RRA => "RRA".into(),
|
|
||||||
DAA => "DAA".into(),
|
|
||||||
CPL => "CPL".into(),
|
|
||||||
SCF => "SCF".into(),
|
|
||||||
CCF => "CCF".into(),
|
|
||||||
HALT => "HALT".into(),
|
|
||||||
LD(LDTarget::Register(left), LDSource::Register(right)) => {
|
|
||||||
format!("LD {:?}, {:?}", left, right).into()
|
|
||||||
}
|
|
||||||
ADD(AddTarget::A, AddSource::Register(reg)) => format!("ADD A, {:?}", reg).into(),
|
|
||||||
ADC(AluSource::Register(reg)) => format!("ADC {:?}", reg).into(),
|
|
||||||
SUB(AluSource::Register(reg)) => format!("SUB {:?}", reg).into(),
|
|
||||||
SBC(AluSource::Register(reg)) => format!("SBC {:?}", reg).into(),
|
|
||||||
AND(AluSource::Register(reg)) => format!("AND {:?}", reg).into(),
|
|
||||||
XOR(AluSource::Register(reg)) => format!("XOR {:?}", reg).into(),
|
|
||||||
OR(AluSource::Register(reg)) => format!("OR {:?}", reg).into(),
|
|
||||||
CP(AluSource::Register(reg)) => format!("CP {:?}", reg).into(),
|
|
||||||
RET(JpCond::Always) => "RET".into(),
|
|
||||||
RET(cond) => format!("RET {:?}", cond).into(),
|
|
||||||
LD(LDTarget::IoWithImmediateOffset, LDSource::A) => {
|
|
||||||
format!("LD ({:#06X}) , A", 0xFF00 + imm_byte as u16).into()
|
|
||||||
}
|
|
||||||
ADD(AddTarget::SP, AddSource::ImmediateSignedByte) => {
|
|
||||||
format!("ADD SP, {}", imm_byte as i8).into()
|
|
||||||
}
|
|
||||||
LD(LDTarget::A, LDSource::IoWithImmediateOffset) => {
|
|
||||||
format!("LD A, ({:#06X})", 0xFF00 + imm_byte as u16).into()
|
|
||||||
}
|
|
||||||
LDHL => format!("LD HL, SP + {}", imm_byte as i8).into(),
|
|
||||||
POP(rp) => format!("POP {:?}", rp).into(),
|
|
||||||
RETI => "RETI".into(),
|
|
||||||
JP(JpCond::Always, JpLoc::HL) => "JP HL".into(),
|
|
||||||
LD(LDTarget::SP, LDSource::HL) => "LD SP, HL".into(),
|
|
||||||
JP(JpCond::Always, JpLoc::ImmediateWord) => format!("JP {:#06X}", imm_word).into(),
|
|
||||||
JP(cond, JpLoc::ImmediateWord) => format!("JP {:?} {:#06X}", cond, imm_word).into(),
|
|
||||||
LD(LDTarget::IoWithC, LDSource::A) => "LD (0xFF00 + C), A".into(),
|
|
||||||
LD(LDTarget::IndirectImmediateWord, LDSource::A) => {
|
|
||||||
format!("LD ({:#06X}), A", imm_byte).into()
|
|
||||||
}
|
|
||||||
LD(LDTarget::A, LDSource::IoWithC) => "LD A, (0xFF00 + C)".into(),
|
|
||||||
LD(LDTarget::A, LDSource::IndirectImmediateWord) => {
|
|
||||||
format!("LD A, ({:#06X})", imm_word).into()
|
|
||||||
}
|
|
||||||
DI => "DI".into(),
|
|
||||||
EI => "EI".into(),
|
|
||||||
CALL(cond) => format!("CALL {:?} {:#06X}", cond, imm_word).into(),
|
|
||||||
PUSH(rp) => format!("PUSH {:?}", rp).into(),
|
|
||||||
ADD(AddTarget::A, AddSource::ImmediateByte) => {
|
|
||||||
format!("ADD A, {:#04X}", imm_byte).into()
|
|
||||||
}
|
|
||||||
ADC(AluSource::ImmediateByte) => format!("ADC {:#04X}", imm_byte).into(),
|
|
||||||
SUB(AluSource::ImmediateByte) => format!("SUB {:#04X}", imm_byte).into(),
|
|
||||||
SBC(AluSource::ImmediateByte) => format!("SBC {:#04X}", imm_byte).into(),
|
|
||||||
AND(AluSource::ImmediateByte) => format!("AND {:#04X}", imm_byte).into(),
|
|
||||||
XOR(AluSource::ImmediateByte) => format!("XOR {:#04X}", imm_byte).into(),
|
|
||||||
OR(AluSource::ImmediateByte) => format!("OR {:#04X}", imm_byte).into(),
|
|
||||||
CP(AluSource::ImmediateByte) => format!("CP {:#04X}", imm_byte).into(),
|
|
||||||
RST(v) => format!("RST {:#04X}", v).into(),
|
|
||||||
|
|
||||||
// Prefixed Instructions
|
|
||||||
RLC(reg) => format!("RLC {:?}", reg).into(),
|
|
||||||
RRC(reg) => format!("RRC {:?}", reg).into(),
|
|
||||||
RL(reg) => format!("RL {:?}", reg).into(),
|
|
||||||
RR(reg) => format!("RR {:?}", reg).into(),
|
|
||||||
SLA(reg) => format!("SLA {:?}", reg).into(),
|
|
||||||
SRA(reg) => format!("SRA {:?}", reg).into(),
|
|
||||||
SWAP(reg) => format!("SWAP {:?}", reg).into(),
|
|
||||||
SRL(reg) => format!("SRL {:?}", reg).into(),
|
|
||||||
BIT(bit, reg) => format!("BIT {}, {:?}", bit, reg).into(),
|
|
||||||
RES(bit, reg) => format!("RES {}, {:?}", bit, reg).into(),
|
|
||||||
SET(bit, reg) => format!("SET {}, {:?}", bit, reg).into(),
|
|
||||||
_ => unreachable!("{:?} is an illegal instruction", instr),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod delta {
|
|
||||||
use super::super::add::{Source as AddSource, Target as AddTarget};
|
|
||||||
use super::super::alu::Source as AluSource;
|
|
||||||
use super::super::jump::{JpCond, JpLoc};
|
|
||||||
use super::super::load::{Source as LDSource, Target as LDTarget};
|
|
||||||
use super::super::{AllRegisters, Instruction};
|
|
||||||
|
|
||||||
pub(super) fn pc_inc_count(instr: Instruction) -> u16 {
|
|
||||||
use Instruction::*;
|
|
||||||
|
|
||||||
match instr {
|
|
||||||
// Unprefixed
|
|
||||||
NOP => 0,
|
|
||||||
LD(LDTarget::IndirectImmediateWord, LDSource::SP) => 2,
|
|
||||||
STOP => 0,
|
|
||||||
JR(_) => 1,
|
|
||||||
LD(LDTarget::Group1(_), LDSource::ImmediateWord) => 2,
|
|
||||||
ADD(AddTarget::HL, AddSource::Group1(_)) => 0,
|
|
||||||
LD(LDTarget::IndirectGroup2(_), LDSource::A) => 0,
|
|
||||||
LD(LDTarget::A, LDSource::IndirectGroup2(_)) => 0,
|
|
||||||
INC(AllRegisters::Group1(_)) => 0,
|
|
||||||
DEC(AllRegisters::Group1(_)) => 0,
|
|
||||||
INC(AllRegisters::Register(_)) => 0,
|
|
||||||
DEC(AllRegisters::Register(_)) => 0,
|
|
||||||
LD(LDTarget::Register(_), LDSource::ImmediateByte) => 1,
|
|
||||||
RLCA => 0,
|
|
||||||
RRCA => 0,
|
|
||||||
RLA => 0,
|
|
||||||
RRA => 0,
|
|
||||||
DAA => 0,
|
|
||||||
CPL => 0,
|
|
||||||
SCF => 0,
|
|
||||||
CCF => 0,
|
|
||||||
HALT => 0,
|
|
||||||
LD(LDTarget::Register(_), LDSource::Register(_)) => 0,
|
|
||||||
ADD(AddTarget::A, AddSource::Register(_)) => 0,
|
|
||||||
ADC(AluSource::Register(_)) => 0,
|
|
||||||
SUB(AluSource::Register(_)) => 0,
|
|
||||||
SBC(AluSource::Register(_)) => 0,
|
|
||||||
AND(AluSource::Register(_)) => 0,
|
|
||||||
XOR(AluSource::Register(_)) => 0,
|
|
||||||
OR(AluSource::Register(_)) => 0,
|
|
||||||
CP(AluSource::Register(_)) => 0,
|
|
||||||
RET(_) => 0,
|
|
||||||
LD(LDTarget::IoWithImmediateOffset, LDSource::A) => 1,
|
|
||||||
ADD(AddTarget::SP, AddSource::ImmediateSignedByte) => 1,
|
|
||||||
LD(LDTarget::A, LDSource::IoWithImmediateOffset) => 1,
|
|
||||||
LDHL => 1,
|
|
||||||
POP(_) => 0,
|
|
||||||
RETI => 0,
|
|
||||||
JP(JpCond::Always, JpLoc::HL) => 0,
|
|
||||||
LD(LDTarget::SP, LDSource::HL) => 0,
|
|
||||||
JP(_, JpLoc::ImmediateWord) => 2,
|
|
||||||
LD(LDTarget::IoWithC, LDSource::A) => 0,
|
|
||||||
LD(LDTarget::IndirectImmediateWord, LDSource::A) => 2,
|
|
||||||
LD(LDTarget::A, LDSource::IoWithC) => 0,
|
|
||||||
LD(LDTarget::A, LDSource::IndirectImmediateWord) => 2,
|
|
||||||
DI => 0,
|
|
||||||
EI => 0,
|
|
||||||
CALL(_) => 0,
|
|
||||||
PUSH(_) => 0,
|
|
||||||
ADD(AddTarget::A, AddSource::ImmediateByte) => 1,
|
|
||||||
ADC(AluSource::ImmediateByte) => 1,
|
|
||||||
SUB(AluSource::ImmediateByte) => 1,
|
|
||||||
SBC(AluSource::ImmediateByte) => 1,
|
|
||||||
AND(AluSource::ImmediateByte) => 1,
|
|
||||||
XOR(AluSource::ImmediateByte) => 1,
|
|
||||||
OR(AluSource::ImmediateByte) => 1,
|
|
||||||
CP(AluSource::ImmediateByte) => 1,
|
|
||||||
RST(_) => 0,
|
|
||||||
|
|
||||||
// Prefixed
|
|
||||||
RLC(_) => 0,
|
|
||||||
RRC(_) => 0,
|
|
||||||
RL(_) => 0,
|
|
||||||
RR(_) => 0,
|
|
||||||
SLA(_) => 0,
|
|
||||||
SRA(_) => 0,
|
|
||||||
SWAP(_) => 0,
|
|
||||||
SRL(_) => 0,
|
|
||||||
BIT(_, _) => 0,
|
|
||||||
RES(_, _) => 0,
|
|
||||||
SET(_, _) => 0,
|
|
||||||
_ => unreachable!("{:?} is an illegal instruction", instr),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,20 +1,11 @@
|
||||||
use bitfield::bitfield;
|
use bitfield::bitfield;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct Interrupt {
|
pub(crate) struct Interrupt {
|
||||||
pub(crate) flag: InterruptFlag,
|
pub(crate) flag: InterruptFlag,
|
||||||
pub(crate) enable: InterruptEnable,
|
pub(crate) enable: InterruptEnable,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Interrupt {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
flag: InterruptFlag(0),
|
|
||||||
enable: InterruptEnable(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
pub struct InterruptEnable(u8);
|
pub struct InterruptEnable(u8);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
|
@ -32,6 +23,12 @@ impl Clone for InterruptEnable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for InterruptEnable {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for InterruptEnable {
|
impl From<u8> for InterruptEnable {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
@ -61,6 +58,12 @@ impl Clone for InterruptFlag {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for InterruptFlag {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for InterruptFlag {
|
impl From<u8> for InterruptFlag {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType};
|
use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType};
|
||||||
use winit::event::{ElementState, KeyboardInput, VirtualKeyCode};
|
use winit_input_helper::WinitInputHelper;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Joypad {
|
pub struct Joypad {
|
||||||
|
@ -110,39 +110,72 @@ impl ButtonEvent {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) {
|
||||||
pub(crate) fn handle_keyboard_input(pad: &mut Joypad, key: KeyboardInput) {
|
use winit::event::VirtualKeyCode;
|
||||||
|
|
||||||
|
// TODO: What do I have to do to get a match statement here?
|
||||||
|
|
||||||
let state = &mut pad.ext;
|
let state = &mut pad.ext;
|
||||||
let irq = &mut pad.interrupt;
|
let irq = &mut pad.interrupt;
|
||||||
|
|
||||||
match key.state {
|
if input.key_pressed(VirtualKeyCode::Down) {
|
||||||
ElementState::Pressed => match key.virtual_keycode {
|
state.dpad_down.update(true, irq);
|
||||||
Some(VirtualKeyCode::Down) => state.dpad_down.update(true, irq),
|
}
|
||||||
Some(VirtualKeyCode::Up) => state.dpad_up.update(true, irq),
|
if input.key_released(VirtualKeyCode::Down) {
|
||||||
Some(VirtualKeyCode::Left) => state.dpad_left.update(true, irq),
|
state.dpad_down.update(false, irq);
|
||||||
Some(VirtualKeyCode::Right) => state.dpad_right.update(true, irq),
|
}
|
||||||
Some(VirtualKeyCode::Return) => state.start.update(true, irq),
|
|
||||||
Some(VirtualKeyCode::RShift) => state.select.update(true, irq),
|
if input.key_pressed(VirtualKeyCode::Up) {
|
||||||
Some(VirtualKeyCode::Z) => state.south.update(true, irq),
|
state.dpad_up.update(true, irq);
|
||||||
Some(VirtualKeyCode::X) => state.east.update(true, irq),
|
}
|
||||||
None | Some(_) => {}
|
if input.key_released(VirtualKeyCode::Up) {
|
||||||
},
|
state.dpad_up.update(false, irq);
|
||||||
ElementState::Released => match key.virtual_keycode {
|
}
|
||||||
Some(VirtualKeyCode::Down) => state.dpad_down.update(false, irq),
|
|
||||||
Some(VirtualKeyCode::Up) => state.dpad_up.update(false, irq),
|
if input.key_pressed(VirtualKeyCode::Left) {
|
||||||
Some(VirtualKeyCode::Left) => state.dpad_left.update(false, irq),
|
state.dpad_left.update(true, irq);
|
||||||
Some(VirtualKeyCode::Right) => state.dpad_right.update(false, irq),
|
}
|
||||||
Some(VirtualKeyCode::Return) => state.start.update(false, irq),
|
if input.key_released(VirtualKeyCode::Left) {
|
||||||
Some(VirtualKeyCode::RShift) => state.select.update(false, irq),
|
state.dpad_left.update(false, irq);
|
||||||
Some(VirtualKeyCode::Z) => state.south.update(false, irq),
|
}
|
||||||
Some(VirtualKeyCode::X) => state.east.update(false, irq),
|
|
||||||
None | Some(_) => {}
|
if input.key_pressed(VirtualKeyCode::Right) {
|
||||||
},
|
state.dpad_right.update(true, irq);
|
||||||
|
}
|
||||||
|
if input.key_released(VirtualKeyCode::Right) {
|
||||||
|
state.dpad_right.update(false, irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.key_pressed(VirtualKeyCode::T) {
|
||||||
|
state.start.update(true, irq);
|
||||||
|
}
|
||||||
|
if input.key_released(VirtualKeyCode::T) {
|
||||||
|
state.start.update(false, irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.key_pressed(VirtualKeyCode::Y) {
|
||||||
|
state.select.update(true, irq);
|
||||||
|
}
|
||||||
|
if input.key_released(VirtualKeyCode::Y) {
|
||||||
|
state.select.update(false, irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.key_pressed(VirtualKeyCode::Z) {
|
||||||
|
state.south.update(true, irq);
|
||||||
|
}
|
||||||
|
if input.key_released(VirtualKeyCode::Z) {
|
||||||
|
state.south.update(false, irq);
|
||||||
|
}
|
||||||
|
|
||||||
|
if input.key_pressed(VirtualKeyCode::X) {
|
||||||
|
state.east.update(true, irq);
|
||||||
|
}
|
||||||
|
if input.key_released(VirtualKeyCode::X) {
|
||||||
|
state.east.update(false, irq);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline]
|
pub fn handle_gamepad_input(pad: &mut Joypad, event: GamepadEvent) {
|
||||||
pub(crate) fn handle_gamepad_input(pad: &mut Joypad, event: GamepadEvent) {
|
|
||||||
use Button::*;
|
use Button::*;
|
||||||
use GamepadEventType::*;
|
use GamepadEventType::*;
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,4 @@
|
||||||
#![allow(clippy::derivable_impls)] // Will remove this if bitfield-rs allows default impls
|
pub use apu::gen::AudioSPSC;
|
||||||
|
|
||||||
pub use apu::gen::init as spsc_init;
|
|
||||||
pub type Cycle = u64;
|
pub type Cycle = u64;
|
||||||
|
|
||||||
pub const GB_WIDTH: usize = 160;
|
pub const GB_WIDTH: usize = 160;
|
||||||
|
@ -11,7 +9,6 @@ mod bus;
|
||||||
mod cartridge;
|
mod cartridge;
|
||||||
mod cpu;
|
mod cpu;
|
||||||
pub mod emu;
|
pub mod emu;
|
||||||
pub mod gui;
|
|
||||||
mod high_ram;
|
mod high_ram;
|
||||||
mod instruction;
|
mod instruction;
|
||||||
mod interrupt;
|
mod interrupt;
|
||||||
|
|
192
src/main.rs
192
src/main.rs
|
@ -1,126 +1,144 @@
|
||||||
use std::path::PathBuf;
|
use std::convert::TryInto;
|
||||||
use std::time::Instant;
|
|
||||||
|
|
||||||
use clap::{arg, command, value_parser};
|
use anyhow::{anyhow, Result};
|
||||||
use gb::gui::{EmuMode, Gui};
|
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
|
||||||
use gb::{emu, gui};
|
use gb::emu::build::EmulatorBuilder;
|
||||||
|
use gb::{AudioSPSC, Cycle, GB_HEIGHT, GB_WIDTH};
|
||||||
use gilrs::Gilrs;
|
use gilrs::Gilrs;
|
||||||
|
use pixels::{PixelsBuilder, SurfaceTexture};
|
||||||
use rodio::{OutputStream, Sink};
|
use rodio::{OutputStream, Sink};
|
||||||
use tracing_subscriber::EnvFilter;
|
use winit::dpi::{LogicalSize, PhysicalSize};
|
||||||
use winit::event::{Event, WindowEvent};
|
use winit::event::{Event, VirtualKeyCode};
|
||||||
use winit::event_loop::{EventLoop, EventLoopBuilder};
|
use winit::event_loop::{ControlFlow, EventLoop};
|
||||||
|
use winit::window::{Window, WindowBuilder};
|
||||||
|
use winit_input_helper::WinitInputHelper;
|
||||||
|
|
||||||
const AUDIO_ENABLED: bool = true;
|
const WINDOW_SCALE: usize = 3;
|
||||||
|
const AUDIO_ENABLED: bool = false;
|
||||||
|
|
||||||
fn main() {
|
fn main() -> Result<()> {
|
||||||
let m = command!()
|
let app = App::new(crate_name!())
|
||||||
|
.version(crate_version!())
|
||||||
|
.author(crate_authors!())
|
||||||
|
.about(crate_description!());
|
||||||
|
|
||||||
|
let m = app
|
||||||
.arg(
|
.arg(
|
||||||
arg!(-b --boot <FILE> "path to boot ROM")
|
Arg::with_name("rom")
|
||||||
.required(false)
|
.value_name("ROM_FILE")
|
||||||
.value_parser(value_parser!(PathBuf)),
|
.takes_value(true)
|
||||||
|
.required(true)
|
||||||
|
.index(1)
|
||||||
|
.help("Path to the Game ROM"),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
arg!([ROM_FILE] "path to game ROM")
|
Arg::with_name("boot")
|
||||||
.required(true)
|
.short("b")
|
||||||
.value_parser(value_parser!(PathBuf)),
|
.long("boot")
|
||||||
|
.value_name("FILE")
|
||||||
|
.takes_value(true)
|
||||||
|
.help("Path to Boot ROM"),
|
||||||
)
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
// Set up subscriber
|
let mut emu_build =
|
||||||
if std::env::var("RUST_LOG").is_err() {
|
EmulatorBuilder::new().with_cart(m.value_of("rom").expect("ROM path provided"))?;
|
||||||
std::env::set_var("RUST_LOG", "gb=info");
|
|
||||||
|
if let Some(path) = m.value_of("boot") {
|
||||||
|
emu_build = emu_build.with_boot(path)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
tracing_subscriber::fmt::fmt()
|
let mut emu = emu_build.finish();
|
||||||
.with_env_filter(EnvFilter::from_default_env())
|
let rom_title = emu.title();
|
||||||
.init();
|
|
||||||
|
|
||||||
// Init CPU
|
|
||||||
let mut cpu = match m.get_one::<PathBuf>("boot") {
|
|
||||||
Some(path) => {
|
|
||||||
tracing::info!("User-provided boot ROM");
|
|
||||||
emu::from_boot_rom(path).expect("initialize emulator with custom boot rom")
|
|
||||||
}
|
|
||||||
None => {
|
|
||||||
tracing::info!("Built-in boot ROM");
|
|
||||||
Default::default()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load ROM if filepath was provided
|
|
||||||
if let Some(path) = m.get_one::<PathBuf>("ROM_FILE") {
|
|
||||||
tracing::info!("User-provided cartridge ROM");
|
|
||||||
emu::read_game_rom(&mut cpu, path).expect("read game rom from path");
|
|
||||||
}
|
|
||||||
emu::load_save(&mut cpu);
|
|
||||||
let rom_title = emu::rom_title(&cpu).to_string();
|
|
||||||
|
|
||||||
tracing::info!("Initialize Gamepad");
|
|
||||||
let mut gamepad = Gilrs::new().expect("Initialize Controller Support");
|
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, rom_title)?;
|
||||||
|
|
||||||
|
let mut pixels = {
|
||||||
|
let size = window.inner_size();
|
||||||
|
let surface_texture = SurfaceTexture::new(size.width, size.height, &window);
|
||||||
|
|
||||||
|
PixelsBuilder::new(GB_WIDTH as u32, GB_HEIGHT as u32, surface_texture)
|
||||||
|
.enable_vsync(false)
|
||||||
|
.build()?
|
||||||
|
};
|
||||||
|
|
||||||
// Initialize Audio
|
// Initialize Audio
|
||||||
let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio");
|
let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio");
|
||||||
|
|
||||||
if AUDIO_ENABLED {
|
if AUDIO_ENABLED {
|
||||||
let (prod, cons) = gb::spsc_init();
|
let spsc: AudioSPSC<f32> = Default::default();
|
||||||
|
let (prod, cons) = spsc.init();
|
||||||
let sink = {
|
let sink = {
|
||||||
let s = Sink::try_new(&stream_handle).expect("create sink from audio stream handle");
|
let s = Sink::try_new(&stream_handle)?;
|
||||||
s.append(cons);
|
s.append(cons);
|
||||||
s.set_volume(0.1);
|
s.set_volume(0.1);
|
||||||
s
|
s
|
||||||
};
|
};
|
||||||
|
|
||||||
emu::set_audio_prod(&mut cpu, prod);
|
emu.set_prod(prod);
|
||||||
|
|
||||||
tracing::info!("Spawn Audio Thread");
|
|
||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
sink.sleep_until_end();
|
sink.sleep_until_end();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set up state for the Immediate-mode GUI
|
let mut cycle_count: Cycle = Default::default();
|
||||||
let event_loop: EventLoop<Event<()>> = EventLoopBuilder::with_user_event().build();
|
|
||||||
|
|
||||||
let mut app = Gui::new(rom_title, &event_loop, &cpu);
|
|
||||||
let mut last_key = gui::unused_key(); // TODO: Fix this awful impl
|
|
||||||
|
|
||||||
// used for egui animations
|
|
||||||
let start_time = Instant::now();
|
|
||||||
|
|
||||||
event_loop.run(move |event, _, control_flow| {
|
event_loop.run(move |event, _, control_flow| {
|
||||||
app.handle_event(&event);
|
if let Event::RedrawRequested(_) = event {
|
||||||
|
if pixels
|
||||||
match event {
|
.render()
|
||||||
Event::MainEventsCleared => {
|
.map_err(|e| anyhow!("pixels.render() failed: {}", e))
|
||||||
app.maybe_quit(&cpu, control_flow);
|
.is_err()
|
||||||
|
{
|
||||||
match app.mode {
|
*control_flow = ControlFlow::Exit;
|
||||||
EmuMode::Running => emu::run_frame(&mut cpu, &mut gamepad, last_key),
|
return;
|
||||||
EmuMode::StepFrame if gui::kbd::space_released(&last_key) => {
|
|
||||||
emu::run_frame(&mut cpu, &mut gamepad, last_key)
|
|
||||||
}
|
|
||||||
EmuMode::Step if gui::kbd::space_released(&last_key) => {
|
|
||||||
emu::run(&mut cpu, &mut gamepad, last_key, 4);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Input has been consumed, reset it
|
|
||||||
last_key = gui::unused_key();
|
|
||||||
|
|
||||||
app.request_redraw();
|
|
||||||
}
|
}
|
||||||
Event::RedrawRequested(..) => {
|
}
|
||||||
app.update_time(start_time.elapsed().as_secs_f64());
|
|
||||||
app.paint(&cpu);
|
if input.update(&event) {
|
||||||
|
if input.key_pressed(VirtualKeyCode::Escape) || input.quit() {
|
||||||
|
*control_flow = ControlFlow::Exit;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(size) = input.window_resized() {
|
||||||
|
pixels.resize_surface(size.width, size.height);
|
||||||
|
}
|
||||||
|
|
||||||
|
cycle_count += gb::emu::run_frame(&mut emu, &mut gamepad, &input);
|
||||||
|
|
||||||
|
if cycle_count >= gb::emu::CYCLES_IN_FRAME {
|
||||||
|
cycle_count %= gb::emu::CYCLES_IN_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();
|
||||||
}
|
}
|
||||||
Event::WindowEvent { event, .. } => match event {
|
|
||||||
WindowEvent::Resized(size) => app.resize(size),
|
|
||||||
WindowEvent::CloseRequested => emu::save_and_exit(&cpu, control_flow),
|
|
||||||
WindowEvent::KeyboardInput { input, .. } => last_key = input,
|
|
||||||
_ => {}
|
|
||||||
},
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> {
|
||||||
|
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_min_inner_size(logical)
|
||||||
|
.with_inner_size(physical)
|
||||||
|
.with_resizable(true)
|
||||||
|
.build(event_loop)?)
|
||||||
|
}
|
||||||
|
|
732
src/ppu.rs
732
src/ppu.rs
File diff suppressed because it is too large
Load Diff
|
@ -1,6 +1,6 @@
|
||||||
use crate::Cycle;
|
use crate::Cycle;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct DirectMemoryAccess {
|
pub(crate) struct DirectMemoryAccess {
|
||||||
pub(crate) state: DmaState,
|
pub(crate) state: DmaState,
|
||||||
cycle: Cycle,
|
cycle: Cycle,
|
||||||
|
@ -8,16 +8,6 @@ pub(crate) struct DirectMemoryAccess {
|
||||||
pub(crate) start: DmaAddress,
|
pub(crate) start: DmaAddress,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for DirectMemoryAccess {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
state: DmaState::Disabled,
|
|
||||||
cycle: Default::default(),
|
|
||||||
start: Default::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl DirectMemoryAccess {
|
impl DirectMemoryAccess {
|
||||||
pub(crate) fn tick(&mut self) -> Option<(u16, u16)> {
|
pub(crate) fn tick(&mut self) -> Option<(u16, u16)> {
|
||||||
match self.state {
|
match self.state {
|
||||||
|
@ -79,6 +69,12 @@ pub(crate) enum DmaState {
|
||||||
Transferring,
|
Transferring,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for DmaState {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Disabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Default)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub(crate) struct DmaAddress(Option<u16>);
|
pub(crate) struct DmaAddress(Option<u16>);
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,12 @@ impl Clone for LCDStatus {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for LCDStatus {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0x80) // bit 7 is always 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<LCDStatus> for u8 {
|
impl From<LCDStatus> for u8 {
|
||||||
fn from(status: LCDStatus) -> Self {
|
fn from(status: LCDStatus) -> Self {
|
||||||
status.0
|
status.0
|
||||||
|
@ -61,6 +67,12 @@ impl From<PpuMode> for u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for PpuMode {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::HBlank
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
pub struct LCDControl(u8);
|
pub struct LCDControl(u8);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
|
@ -81,6 +93,12 @@ impl Clone for LCDControl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for LCDControl {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for LCDControl {
|
impl From<u8> for LCDControl {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
@ -124,6 +142,12 @@ impl From<TileMapAddress> for u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for TileMapAddress {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::X9800
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum TileDataAddress {
|
pub enum TileDataAddress {
|
||||||
X8800 = 0,
|
X8800 = 0,
|
||||||
|
@ -146,6 +170,12 @@ impl From<TileDataAddress> for u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for TileDataAddress {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::X8800
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum ObjectSize {
|
pub enum ObjectSize {
|
||||||
Eight = 0,
|
Eight = 0,
|
||||||
|
@ -153,7 +183,7 @@ pub enum ObjectSize {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ObjectSize {
|
impl ObjectSize {
|
||||||
pub(crate) fn size(&self) -> u8 {
|
pub(crate) fn as_u8(&self) -> u8 {
|
||||||
use ObjectSize::*;
|
use ObjectSize::*;
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
|
@ -179,6 +209,12 @@ impl From<ObjectSize> for u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ObjectSize {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Eight
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
pub struct BackgroundPalette(u8);
|
pub struct BackgroundPalette(u8);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
|
@ -207,6 +243,12 @@ impl Clone for BackgroundPalette {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for BackgroundPalette {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for BackgroundPalette {
|
impl From<u8> for BackgroundPalette {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
@ -246,6 +288,12 @@ impl Clone for ObjectPalette {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ObjectPalette {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for ObjectPalette {
|
impl From<u8> for ObjectPalette {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
@ -313,12 +361,24 @@ impl From<ObjectFlags> for u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ObjectFlags {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum ObjectPaletteKind {
|
pub enum ObjectPaletteKind {
|
||||||
Zero = 0,
|
Zero = 0,
|
||||||
One = 1,
|
One = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ObjectPaletteKind {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for ObjectPaletteKind {
|
impl From<u8> for ObjectPaletteKind {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
match byte & 0b01 {
|
match byte & 0b01 {
|
||||||
|
@ -335,7 +395,7 @@ impl From<ObjectPaletteKind> for u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum RenderPriority {
|
pub enum RenderPriority {
|
||||||
Object = 0,
|
Object = 0,
|
||||||
BackgroundAndWindow = 1,
|
BackgroundAndWindow = 1,
|
||||||
|
@ -357,6 +417,12 @@ impl From<RenderPriority> for u8 {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for RenderPriority {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Object
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub enum GrayShade {
|
pub enum GrayShade {
|
||||||
White = 0,
|
White = 0,
|
||||||
|
@ -376,6 +442,12 @@ impl GrayShade {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for GrayShade {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::White
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for GrayShade {
|
impl From<u8> for GrayShade {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
match byte & 0b11 {
|
match byte & 0b11 {
|
||||||
|
|
|
@ -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;
|
use bitfield::bitfield;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct Serial {
|
pub(crate) struct Serial {
|
||||||
/// 0xFF01 | SB - Serial Transfer Data
|
/// 0xFF01 | SB - Serial Transfer Data
|
||||||
pub(crate) next: u8,
|
pub(crate) next: u8,
|
||||||
|
@ -8,15 +8,6 @@ pub(crate) struct Serial {
|
||||||
pub(crate) ctrl: SerialControl,
|
pub(crate) ctrl: SerialControl,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Serial {
|
|
||||||
fn default() -> Self {
|
|
||||||
Self {
|
|
||||||
next: Default::default(),
|
|
||||||
ctrl: SerialControl(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitfield! {
|
bitfield! {
|
||||||
pub struct SerialControl(u8);
|
pub struct SerialControl(u8);
|
||||||
impl Debug;
|
impl Debug;
|
||||||
|
@ -32,6 +23,12 @@ impl Clone for SerialControl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for SerialControl {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for SerialControl {
|
impl From<u8> for SerialControl {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
@ -50,6 +47,12 @@ enum ShiftClock {
|
||||||
Internal = 1,
|
Internal = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ShiftClock {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::External
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for ShiftClock {
|
impl From<u8> for ShiftClock {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
match byte & 0b01 {
|
match byte & 0b01 {
|
||||||
|
@ -66,6 +69,12 @@ enum ClockSpeed {
|
||||||
Fast = 1,
|
Fast = 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ClockSpeed {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Normal
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for ClockSpeed {
|
impl From<u8> for ClockSpeed {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
match byte & 0b01 {
|
match byte & 0b01 {
|
||||||
|
|
39
src/timer.rs
39
src/timer.rs
|
@ -21,6 +21,17 @@ impl Timer {
|
||||||
use State::*;
|
use State::*;
|
||||||
use TimerSpeed::*;
|
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);
|
self.divider = self.divider.wrapping_add(1);
|
||||||
|
|
||||||
// Get Bit Position
|
// Get Bit Position
|
||||||
|
@ -42,17 +53,6 @@ impl Timer {
|
||||||
}
|
}
|
||||||
|
|
||||||
self.and_result = Some(new_result);
|
self.and_result = Some(new_result);
|
||||||
|
|
||||||
match self.state {
|
|
||||||
TIMAOverflow(_) | AbortedTIMAOverflow(_) => self.next(),
|
|
||||||
LoadTIMA => {
|
|
||||||
self.counter = self.modulo;
|
|
||||||
self.interrupt = true;
|
|
||||||
|
|
||||||
self.next();
|
|
||||||
}
|
|
||||||
Normal => {}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// 0xFF05 | TIMA - Timer Counter
|
/// 0xFF05 | TIMA - Timer Counter
|
||||||
|
@ -70,7 +70,7 @@ impl Timer {
|
||||||
self.counter = byte;
|
self.counter = byte;
|
||||||
self.state = AbortedTIMAOverflow(step);
|
self.state = AbortedTIMAOverflow(step);
|
||||||
}
|
}
|
||||||
LoadTIMA => { /* Ignored */ }
|
LoadTMA => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,8 +95,9 @@ impl Timer {
|
||||||
use State::*;
|
use State::*;
|
||||||
|
|
||||||
self.state = match self.state {
|
self.state = match self.state {
|
||||||
Normal | LoadTIMA | AbortedTIMAOverflow(3) => Normal,
|
Normal | LoadTMA => Normal,
|
||||||
TIMAOverflow(3) => LoadTIMA,
|
AbortedTIMAOverflow(4) => Normal,
|
||||||
|
TIMAOverflow(4) => LoadTMA,
|
||||||
AbortedTIMAOverflow(step) => AbortedTIMAOverflow(step + 1),
|
AbortedTIMAOverflow(step) => AbortedTIMAOverflow(step + 1),
|
||||||
TIMAOverflow(step) => TIMAOverflow(step + 1),
|
TIMAOverflow(step) => TIMAOverflow(step + 1),
|
||||||
}
|
}
|
||||||
|
@ -106,7 +107,7 @@ impl Timer {
|
||||||
impl Default for Timer {
|
impl Default for Timer {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self {
|
Self {
|
||||||
ctrl: TimerControl(0),
|
ctrl: Default::default(),
|
||||||
counter: 0,
|
counter: 0,
|
||||||
modulo: 0,
|
modulo: 0,
|
||||||
divider: 0,
|
divider: 0,
|
||||||
|
@ -157,6 +158,12 @@ impl Clone for TimerControl {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for TimerControl {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<u8> for TimerControl {
|
impl From<u8> for TimerControl {
|
||||||
fn from(byte: u8) -> Self {
|
fn from(byte: u8) -> Self {
|
||||||
Self(byte)
|
Self(byte)
|
||||||
|
@ -174,5 +181,5 @@ enum State {
|
||||||
TIMAOverflow(u8),
|
TIMAOverflow(u8),
|
||||||
AbortedTIMAOverflow(u8),
|
AbortedTIMAOverflow(u8),
|
||||||
Normal,
|
Normal,
|
||||||
LoadTIMA,
|
LoadTMA,
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,13 +11,13 @@ pub(crate) struct WorkRam {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BusIo for WorkRam {
|
impl BusIo for WorkRam {
|
||||||
fn read_byte(&self, addr: u16) -> u8 {
|
|
||||||
self.bank[addr as usize - WORK_RAM_START_ADDRESS]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_byte(&mut self, addr: u16, byte: u8) {
|
fn write_byte(&mut self, addr: u16, byte: u8) {
|
||||||
self.bank[addr as usize - WORK_RAM_START_ADDRESS] = byte;
|
self.bank[addr as usize - WORK_RAM_START_ADDRESS] = byte;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn read_byte(&self, addr: u16) -> u8 {
|
||||||
|
self.bank[addr as usize - WORK_RAM_START_ADDRESS]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for WorkRam {
|
impl Default for WorkRam {
|
||||||
|
@ -42,11 +42,11 @@ impl Default for VariableWorkRam {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BusIo for VariableWorkRam {
|
impl BusIo for VariableWorkRam {
|
||||||
fn read_byte(&self, addr: u16) -> u8 {
|
|
||||||
self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS]
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_byte(&mut self, addr: u16, byte: u8) {
|
fn write_byte(&mut self, addr: u16, byte: u8) {
|
||||||
self.buf[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.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue