Compare commits

...

54 Commits

Author SHA1 Message Date
70e0b5868d chore: code cleanup 2021-11-30 03:59:44 -04:00
a0e3c7c602 chore: remove useless default implementations 2021-11-25 04:07:45 -04:00
ef4f3d9ec6 chore(cart): refactor sections of cartridge code 2021-11-25 03:32:22 -04:00
7f872a554b chore(cart): code cleanup 2021-11-25 03:32:18 -04:00
dda0257655 fix(cart): support cartridge headers w/ manufactor codes 2021-11-25 00:12:35 -04:00
c10816c048 chore: imrpove private and public APIs 2021-11-24 22:12:26 -04:00
2405fd027f chore: remove Emulator struct 2021-11-21 05:27:04 -04:00
939c25ce1a chore: update comment 2021-11-15 22:05:26 -04:00
6f5e863645 chore: drop pixels-rs and add egui w/ wgpu backend 2021-11-15 21:34:24 -04:00
ce5d58e1d2 chore(ppu): rename some structs 2021-11-07 14:37:40 -06:00
ebcd5bec5c fix(cart): reset sub-cycle counting in RTC
specifically when RTC S is written to
2021-11-07 13:05:41 -06:00
f2c49b398c chore(cart): improve code quality of RtcClock 2021-11-05 23:05:36 -03:00
6f76571d6c chore: update README 2021-11-05 22:40:31 -03:00
55da5a29d8 chore: remove more getters 2021-11-01 18:04:22 +09:00
7dfcd44820 chore(emu): remove bus and bus_mut from cpu 2021-11-01 17:56:42 +09:00
80a15039e0 chore(apu): improve accuracy of Channel 1 2021-10-30 21:53:08 +09:00
32405c0734 fix(apu): implement some obscure behaviour for ch1,2 & 3 2021-10-30 20:04:16 +09:00
2ef8fefbb7 chore(apu): implement more obscure behaviour
implement capacitor
2021-10-30 18:41:31 +09:00
548945a4ee chore: remove imports of tracing macros 2021-10-30 16:41:06 +09:00
1526b4a09d fix(cart): improve title parsin
Now both Tokimeki Memorial titles are read properly
2021-10-30 15:48:47 +09:00
4125ea5c74 fix(instr): fix timing issues with select instructions
instr_timing now runs (and fails on everything)
2021-10-30 14:02:28 +09:00
b9519d9b7a chore(bus): stub some CGB IO registers 2021-10-30 14:02:05 +09:00
01278ca83f chore: reduce size of public interface 2021-10-30 10:28:20 +09:00
2bb8bd6d8f chore: update dependencies 2021-10-30 09:37:24 +09:00
080c1e7518 fix(bus): don't panic on non-existent cartridge 2021-10-28 22:19:38 -03:00
765f9d8288 Merge branch 'main' of ssh://musuka.dev:2222/paoda/gb 2021-10-28 22:08:04 -03:00
8780c4a59b chore(emu): remove and replace EmulatorBuilder 2021-10-28 22:07:36 -03:00
e19a540650 chore: upgrade to rust 2021 2021-10-22 01:42:40 -03:00
4cfd951ede chore(ppu): improve bg-sprite priority functionality 2021-10-20 20:08:09 -03:00
de8fe1a367 chore(ppu): remove object buffer iter_mut method 2021-10-20 19:41:27 -03:00
c5fa41b20d chore(ppu): refactor FetcherState 2021-10-20 19:40:29 -03:00
552cfd4a18 chore(ppu): sort OAM Memory 2021-10-20 17:36:38 -03:00
4c516804e4 fix(ppu): refactor and improve accuracy of background fetcher 2021-10-20 16:58:19 -03:00
9b3ab73bb1 fix(ppu): incremental improvements to accuracy of ppu fetcher 2021-10-20 05:21:49 -03:00
d9f1d661ae chore(ppu): refactor behaviour w.r.t window enabling 2021-10-20 04:48:20 -03:00
da83032e24 chore(ppu): use "dot" intead of "cycle" 2021-10-20 03:34:09 -03:00
67e2447d3f chore(ppu): rename window_stat to win_stat 2021-10-20 03:16:37 -03:00
37cf3d92e4 chore(ppu): refactor OAM Scan implementation 2021-10-20 03:14:30 -03:00
293e5762c3 chore: small code-cleanup changes 2021-10-20 02:48:44 -03:00
999f661e6b chore: update cargo.lock 2021-10-14 14:58:25 -03:00
e573411890 chore: update dependencies 2021-10-11 00:06:51 -03:00
352a65b705 chore(apu): update spsc depenency 2021-10-07 15:27:51 -03:00
64230973f1 chore: suggest inline for hot code 2021-09-24 16:16:14 -03:00
dbbf87af52 chore: add a creative-commons boot rom for compat 2021-09-24 16:15:55 -03:00
1440cd1fc7 chore: update README.md 2021-09-24 12:34:48 -03:00
9964b49ce1 fix(ppu): improve accuracy of timer 2021-09-21 12:50:31 -03:00
142231d355 chore(ppu): remoe unnecessary brackets 2021-09-21 12:13:55 -03:00
227928e8ca Revert "fix(ppu): explicity choose to use sign extension"
This reverts commit 1001b0b124.
2021-09-21 12:09:35 -03:00
1001b0b124 fix(ppu): explicity choose to use sign extension 2021-09-21 09:52:12 -03:00
71ce3f43e0 chore: satisfy clippy 2021-09-20 04:15:05 -03:00
ce121864d2 feat: implement cartridge saving
Implemented for MBC1, MBC2, MBC3 and MBC5
2021-09-20 04:13:25 -03:00
e1fe00ab64 fix: have start and select keybinds match other emulators 2021-09-20 03:26:06 -03:00
5882678bc5 chore: inline some functions
Some checks reported errors
continuous-integration/drone/push Build was killed
2021-09-20 01:34:41 -03:00
bcd67cb317 chore: begin refactor of public api 2021-09-20 01:34:21 -03:00
25 changed files with 2182 additions and 1467 deletions

579
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -2,21 +2,27 @@
name = "gb"
version = "0.1.0"
authors = ["Rekai Musuka <rekai@musuka.dev>"]
edition = "2018"
resolver = "2"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "^1.0"
bitfield = "^0.13"
clap = "^2.33"
gilrs = "^0.8"
pixels = "^0.6"
winit = "^0.25"
winit_input_helper = "^0.10"
rodio = "^0.14"
rtrb = "^0.1.4"
anyhow = "1.0"
bitfield = "0.13"
clap = "2.33"
gilrs = "0.8"
winit = "0.25"
egui = "0.15"
wgpu = "0.11"
egui_wgpu_backend = "0.14"
egui_winit_platform = "0.11"
pollster = "0.2"
rodio = "0.14"
rtrb = "0.2"
directories-next = "2.0"
tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["std", "env-filter"] }
thiserror = "1.0.30"
[profile.release]
debug = true

View File

@@ -2,13 +2,37 @@
[![Build Status](https://ci.paoda.moe/api/badges/paoda/gb/status.svg)](https://ci.paoda.moe/paoda/gb)
### Status
* Passes Blargg's cpu_instrs Test
* Renders Background & Window Tiles
* Implements a PPU FIFO
* From [Blargg Test ROMs](https://github.com/L-P/blargg-test-roms/)
* [x] cpu_instrs
* [ ] instr_timing (kind of)
* [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.
### Notes
* [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
* Sound is neither emulated nor stubbed. Upon writing / reading to a APU related register the emulator will panic.
* Code cleanup is pending completion of some minimum viable product of the emulator
### Controls
Controls are defined [here](https://git.musuka.dev/paoda/gb/src/branch/main/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)

BIN
bin/bootix_dmg.bin Normal file

Binary file not shown.

View File

@@ -1,3 +1,4 @@
use crate::apu::gen::SAMPLE_RATE;
use crate::bus::BusIo;
use crate::emu::SM83_CLOCK_SPEED;
use gen::SampleProducer;
@@ -5,8 +6,8 @@ use types::ch1::{Sweep, SweepDirection};
use types::ch3::Volume as Ch3Volume;
use types::ch4::{CounterWidth, Frequency as Ch4Frequency, PolynomialCounter};
use types::common::{EnvelopeDirection, FrequencyHigh, SoundDuty, VolumeEnvelope};
use types::fs::{FrameSequencer, State as FrameSequencerState};
use types::{ChannelControl, SoundOutput};
use types::fs::{FrameSequencer, State as FSState};
use types::{ChannelControl, NRx4, SoundOutput};
pub mod gen;
mod types;
@@ -26,11 +27,13 @@ pub struct Apu {
/// Noise
ch4: Channel4,
sequencer: FrameSequencer,
fs: FrameSequencer,
div_prev: Option<u16>,
prod: Option<SampleProducer<f32>>,
pub(crate) prod: Option<SampleProducer<f32>>,
sample_counter: u64,
cap: f32,
}
impl BusIo for Apu {
@@ -54,7 +57,7 @@ impl BusIo for Apu {
0x26 => self.ctrl.status(self),
0x30..=0x3F => self.ch3.read_byte(addr),
_ => {
eprintln!("Read 0xFF from unused IO register {:#06X} [APU]", addr);
tracing::warn!("Attempted read from {:#06X}", addr);
0xFF
}
}
@@ -66,29 +69,26 @@ impl BusIo for Apu {
0x11 if self.ctrl.enabled => self.ch1.set_duty(byte),
0x12 if self.ctrl.enabled => self.ch1.set_envelope(byte),
0x13 if self.ctrl.enabled => self.ch1.set_freq_lo(byte),
0x14 if self.ctrl.enabled => self.ch1.set_freq_hi(byte),
0x14 if self.ctrl.enabled => self.ch1.set_freq_hi(&self.fs, byte),
0x16 if self.ctrl.enabled => self.ch2.set_duty(byte),
0x17 if self.ctrl.enabled => self.ch2.set_envelope(byte),
0x18 if self.ctrl.enabled => self.ch2.set_freq_lo(byte),
0x19 if self.ctrl.enabled => self.ch2.set_freq_hi(byte),
0x19 if self.ctrl.enabled => self.ch2.set_freq_hi(&self.fs, byte),
0x1A if self.ctrl.enabled => self.ch3.set_dac_enabled(byte),
0x1B if self.ctrl.enabled => self.ch3.set_len(byte),
0x1C if self.ctrl.enabled => self.ch3.set_volume(byte),
0x1D if self.ctrl.enabled => self.ch3.set_freq_lo(byte),
0x1E if self.ctrl.enabled => self.ch3.set_freq_hi(byte),
0x1E if self.ctrl.enabled => self.ch3.set_freq_hi(&self.fs, byte),
0x20 if self.ctrl.enabled => self.ch4.set_len(byte),
0x21 if self.ctrl.enabled => self.ch4.set_envelope(byte),
0x22 if self.ctrl.enabled => self.ch4.set_poly(byte),
0x23 if self.ctrl.enabled => self.ch4.set_frequency(byte),
0x23 if self.ctrl.enabled => self.ch4.set_frequency(&self.fs, byte),
0x24 if self.ctrl.enabled => self.ctrl.set_channel(byte),
0x25 if self.ctrl.enabled => self.ctrl.set_output(byte),
0x26 => self.set_status(byte),
0x30..=0x3F => self.ch3.write_byte(addr, byte),
_ if !self.ctrl.enabled => {}
_ => eprintln!(
"Wrote {:#04X} to unused IO register {:#06X} [APU]",
byte, addr
),
_ => tracing::warn!("Attempted write of {:#04X} to {:#06X}", byte, addr),
}
}
}
@@ -99,9 +99,9 @@ impl Apu {
// Frame Sequencer (512Hz)
if self.is_falling_edge(12, div) {
use FrameSequencerState::*;
use FSState::*;
match self.sequencer.state() {
match self.fs.state() {
Length => self.clock_length(),
LengthAndSweep => {
self.clock_length();
@@ -111,7 +111,7 @@ impl Apu {
Nothing => {}
}
self.sequencer.next();
self.fs.next();
}
self.div_prev = Some(div);
@@ -124,25 +124,33 @@ impl Apu {
if self.sample_counter >= SM83_CLOCK_SPEED {
self.sample_counter %= SM83_CLOCK_SPEED;
if let Some(ref mut prod) = self.prod {
if let Some(prod) = self.prod.as_mut() {
if prod.available_blocking() {
// 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 ch1_left = if left { self.ch1.amplitude() } else { 0.0 };
let ch1_right = if right { self.ch1.amplitude() } else { 0.0 };
let ch1_left = if left { ch1_amplitude } else { 0.0 };
let ch1_right = if right { 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 ch2_left = if left { self.ch2.amplitude() } else { 0.0 };
let ch2_right = if right { self.ch2.amplitude() } else { 0.0 };
let ch2_left = if left { ch2_amplitude } else { 0.0 };
let ch2_right = if right { 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 ch3_left = if left { self.ch3.amplitude() } else { 0.0 };
let ch3_right = if right { self.ch3.amplitude() } else { 0.0 };
let ch3_left = if left { ch3_amplitude } else { 0.0 };
let ch3_right = if right { 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 ch4_left = if left { self.ch4.amplitude() } else { 0.0 };
let ch4_right = if right { self.ch4.amplitude() } else { 0.0 };
let ch4_left = if left { ch4_amplitude } else { 0.0 };
let ch4_right = if right { ch4_amplitude } else { 0.0 };
let left_mixed = (ch1_left + ch2_left + ch3_left + ch4_left) / 4.0;
let right_mixed = (ch1_right + ch2_right + ch3_right + ch4_right) / 4.0;
@@ -164,7 +172,7 @@ impl Apu {
if self.ctrl.enabled {
// Frame Sequencer reset to Step 0
self.sequencer.reset();
self.fs.reset();
// Square Duty units are reset to first step
self.ch1.duty_pos = 0;
@@ -180,10 +188,6 @@ impl Apu {
}
}
pub fn attach_producer(&mut self, prod: SampleProducer<f32>) {
self.prod = Some(prod);
}
fn reset(&mut self) {
self.ch1.sweep = Default::default();
self.ch1.duty = Default::default();
@@ -207,8 +211,8 @@ impl Apu {
self.ch4.poly = Default::default();
self.ch4.freq = Default::default();
self.ctrl.channel = Default::default();
self.ctrl.out = Default::default();
self.ctrl.channel = ChannelControl(0);
self.ctrl.out = SoundOutput(0);
// Disable the Channels
self.ch1.enabled = Default::default();
@@ -217,25 +221,13 @@ impl Apu {
self.ch4.enabled = Default::default();
}
fn process_length(freq_hi: &FrequencyHigh, length_timer: &mut u16, enabled: &mut bool) {
if freq_hi.length_disable() && *length_timer > 0 {
*length_timer -= 1;
fn process_length(freq: &impl NRx4, counter: &mut u16, enabled: &mut bool) {
if freq.length_enable() && *counter > 0 {
*counter -= 1;
// Check in this scope ensures (only) the above subtraction
// made length_timer 0
if *length_timer == 0 {
*enabled = false;
}
}
}
fn ch4_process_length(freq: &Ch4Frequency, length_timer: &mut u16, enabled: &mut bool) {
if freq.length_disable() && *length_timer > 0 {
*length_timer -= 1;
// Check in this scope ensures (only) the above subtraction
// made length_timer 0
if *length_timer == 0 {
if *counter == 0 {
*enabled = false;
}
}
@@ -244,25 +236,25 @@ impl Apu {
fn clock_length(&mut self) {
Self::process_length(
&self.ch1.freq_hi,
&mut self.ch1.length_timer,
&mut self.ch1.length_counter,
&mut self.ch1.enabled,
);
Self::process_length(
&self.ch2.freq_hi,
&mut self.ch2.length_timer,
&mut self.ch2.length_counter,
&mut self.ch2.enabled,
);
Self::process_length(
&self.ch3.freq_hi,
&mut self.ch3.length_timer,
&mut self.ch3.length_counter,
&mut self.ch3.enabled,
);
Self::ch4_process_length(
Self::process_length(
&self.ch4.freq,
&mut self.ch4.length_timer,
&mut self.ch4.length_counter,
&mut self.ch4.enabled,
);
}
@@ -279,7 +271,7 @@ impl Apu {
if self.ch1.sweep_enabled && period != 0 {
let new_freq = self.ch1.calc_sweep_freq();
if new_freq <= 2047 && self.ch1.sweep.shift_count() != 0 {
if new_freq <= 0x7FF && self.ch1.sweep.shift_count() != 0 {
self.ch1.set_frequency(new_freq);
self.ch1.shadow_freq = new_freq;
@@ -337,9 +329,22 @@ impl Apu {
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);
}
#[derive(Debug, Default)]
output
}
}
#[derive(Debug)]
pub(crate) struct SoundControl {
/// 0xFF24 | NR50 - Channel Control
channel: ChannelControl,
@@ -349,6 +354,16 @@ pub(crate) struct SoundControl {
enabled: bool,
}
impl Default for SoundControl {
fn default() -> Self {
Self {
channel: ChannelControl(0),
out: SoundOutput(0),
enabled: Default::default(),
}
}
}
impl SoundControl {
/// 0xFF24 | NR50 - Channel Control
pub(crate) fn channel(&self) -> u8 {
@@ -408,7 +423,7 @@ pub(crate) struct Channel1 {
sweep_enabled: bool,
// Length Functionality
length_timer: u16,
length_counter: u16,
freq_timer: u16,
duty_pos: u8,
@@ -435,11 +450,11 @@ impl Channel1 {
/// 0xFF11 | NR11 - Channel 1 Sound length / Wave pattern duty
pub(crate) fn set_duty(&mut self, byte: u8) {
self.duty = byte.into();
self.length_timer = 64 - self.duty.sound_length() as u16;
self.length_counter = 64 - self.duty.sound_length() as u16;
}
/// 0xFF12 | NR12 - Channel 1 Volume Envelope
pub fn envelope(&self) -> u8 {
pub(crate) fn envelope(&self) -> u8 {
u8::from(self.envelope)
}
@@ -463,25 +478,30 @@ impl Channel1 {
}
/// 0xFF14 | NR14 - Channel 1 Frequency high
pub(crate) fn set_freq_hi(&mut self, byte: u8) {
self.freq_hi = byte.into();
pub(crate) fn set_freq_hi(&mut self, fs: &FrameSequencer, byte: u8) {
let mut new_freq: FrequencyHigh = byte.into();
// If this bit is set, a trigger event occurs
if self.freq_hi.initial() {
if self.is_dac_enabled() {
if new_freq.trigger() {
self.enabled = true;
}
// Length behaviour during trigger event
if self.length_timer == 0 {
self.length_timer = 64;
}
if self.length_counter == 0 {
self.length_counter = 64;
// Envelope Behaviour during trigger event
// length_counter was 0 so length evidently wasn't enabled
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();
// Channel Volume reloaded
self.current_volume = self.envelope.init_vol();
// Sweep behaviour during trigger event
// Channel 1 Sweep Behaviour
let sweep_period = self.sweep.period();
let sweep_shift = self.sweep.shift_count();
@@ -493,7 +513,19 @@ impl Channel1 {
if sweep_shift != 0 {
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) {
@@ -555,7 +587,7 @@ impl Channel1 {
pub(crate) struct Channel2 {
/// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty
duty: SoundDuty,
/// 0xFF17 | NR22 - Channel 2 Volume ENvelope
/// 0xFF17 | NR22 - Channel 2 Volume Envelope
envelope: VolumeEnvelope,
/// 0xFF18 | NR23 - Channel 2 Frequency low (lower 8 bits only)
freq_lo: u8,
@@ -567,7 +599,7 @@ pub(crate) struct Channel2 {
current_volume: u8,
// Length Functionality
length_timer: u16,
length_counter: u16,
freq_timer: u16,
duty_pos: u8,
@@ -584,15 +616,15 @@ impl Channel2 {
/// 0xFF16 | NR21 - Channel 2 Sound length / Wave Pattern Duty
pub(crate) fn set_duty(&mut self, byte: u8) {
self.duty = byte.into();
self.length_timer = 64 - self.duty.sound_length() as u16;
self.length_counter = 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 {
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) {
self.envelope = byte.into();
@@ -612,22 +644,36 @@ impl Channel2 {
}
/// 0xFF19 | NR24 - Channel 2 Frequency high
pub(crate) fn set_freq_hi(&mut self, byte: u8) {
pub(crate) fn set_freq_hi(&mut self, fs: &FrameSequencer, byte: u8) {
let prev_le = self.freq_hi.length_enable();
self.freq_hi = byte.into();
if self.freq_hi.initial() {
// Envelope behaviour during trigger event
obscure::nrx4::length_update(
&mut self.freq_hi,
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();
// reload volume
self.current_volume = self.envelope.init_vol();
// Length behaviour during trigger event
if self.length_timer == 0 {
self.length_timer = 64;
}
if self.is_dac_enabled() {
self.enabled = true;
}
self.enabled = self.is_dac_enabled();
}
}
@@ -678,7 +724,7 @@ pub(crate) struct Channel3 {
wave_ram: [u8; WAVE_PATTERN_RAM_LEN],
// Length Functionality
length_timer: u16,
length_counter: u16,
freq_timer: u16,
offset: u8,
@@ -724,7 +770,7 @@ impl Channel3 {
/// 0xFF1B | NR31 - Sound Length
pub(crate) fn set_len(&mut self, byte: u8) {
self.len = byte;
self.length_timer = 256 - self.len as u16;
self.length_counter = 256 - self.len as u16;
}
/// 0xFF1C | NR32 - Channel 3 Volume
@@ -756,18 +802,32 @@ impl Channel3 {
}
/// 0xFF1E | NR34 - Channel 3 Frequency high
pub(crate) fn set_freq_hi(&mut self, byte: u8) {
pub(crate) fn set_freq_hi(&mut self, fs: &FrameSequencer, byte: u8) {
let prev_le = self.freq_hi.length_enable();
self.freq_hi = byte.into();
if self.freq_hi.initial() {
obscure::nrx4::length_update(
&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
if self.length_timer == 0 {
self.length_timer = 256;
if self.length_counter == 0 {
self.length_counter = 256;
}
if self.dac_enabled {
self.enabled = true;
}
self.freq_timer = (2048 - self.frequency()) * 2;
// reset wave channel's ptr into wave RAM
self.offset = 0;
self.enabled = self.dac_enabled;
}
}
@@ -826,7 +886,7 @@ pub(crate) struct Channel4 {
current_volume: u8,
// Length Functionality
length_timer: u16,
length_counter: u16,
/// Linear Feedback Shift Register (15-bit)
lf_shift: u16,
@@ -840,7 +900,7 @@ impl Channel4 {
/// 0xFF20 | NR41 - Channel 4 Sound Length
pub(crate) fn set_len(&mut self, byte: u8) {
self.len = byte & 0x3F;
self.length_timer = 64 - self.len as u16;
self.length_counter = 64 - self.len as u16;
}
/// 0xFF21 | NR42 - Channel 4 Volume Envelope
@@ -873,25 +933,37 @@ impl Channel4 {
}
/// 0xFF23 | NR44 - Channel 4 Counter / Consecutive Selector and Restart
pub(crate) fn set_frequency(&mut self, byte: u8) {
pub(crate) fn set_frequency(&mut self, fs: &FrameSequencer, byte: u8) {
let prev_le = self.freq.length_enable();
self.freq = byte.into();
if self.freq.initial() {
// Envelope behaviour during trigger event
obscure::nrx4::length_update(
&mut self.freq,
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();
//reload volume
self.current_volume = self.envelope.init_vol();
// Length behaviour during trigger event
if self.length_timer == 0 {
self.length_timer = 64;
}
// LFSR behaviour during trigger event
// LFSR reset
self.lf_shift = 0x7FFF;
if self.is_dac_enabled() {
self.enabled = true;
}
self.enabled = self.is_dac_enabled();
}
}
@@ -936,3 +1008,39 @@ impl Channel4 {
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;
}
}
}
}
}

View File

@@ -5,34 +5,13 @@ pub(crate) const SAMPLE_RATE: u32 = 48000; // Hz
const CHANNEL_COUNT: usize = 2;
const BUFFER_CAPACITY: usize = 2048 * CHANNEL_COUNT; // # of samples * the # of channels
pub struct AudioSPSC<T> {
inner: RingBuffer<T>,
}
impl<T> Default for AudioSPSC<T> {
fn default() -> Self {
Self {
inner: RingBuffer::new(BUFFER_CAPACITY),
}
}
}
impl<T> AudioSPSC<T> {
pub fn new(capacity: usize) -> Self {
Self {
inner: RingBuffer::new(capacity),
}
}
pub fn init(self) -> (SampleProducer<T>, SampleConsumer<T>) {
let (prod, cons) = self.inner.split();
pub fn init<T>() -> (SampleProducer<T>, SampleConsumer<T>) {
let (prod, cons) = RingBuffer::new(BUFFER_CAPACITY);
(
SampleProducer { inner: prod },
SampleConsumer { inner: cons },
)
}
}
pub struct SampleProducer<T> {
inner: Producer<T>,

View File

@@ -1,5 +1,11 @@
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 {
use super::bitfield;
@@ -103,7 +109,7 @@ pub(super) mod ch3 {
}
pub(super) mod ch4 {
use super::bitfield;
use super::{bitfield, NRx4};
bitfield! {
pub struct PolynomialCounter(u8);
@@ -177,17 +183,21 @@ pub(super) mod ch4 {
bitfield! {
pub struct Frequency(u8);
impl Debug;
_initial, _: 7;
_length_disable, _: 6;
_trigger, _: 7;
_length_enable, _set_length_enable: 6;
}
impl Frequency {
pub(crate) fn length_disable(&self) -> bool {
self._length_disable()
impl NRx4 for Frequency {
fn trigger(&self) -> bool {
self._trigger()
}
pub(crate) fn initial(&self) -> bool {
self._initial()
fn length_enable(&self) -> bool {
self._length_enable()
}
fn set_length_enable(&mut self, value: bool) {
self._set_length_enable(value);
}
}
@@ -218,23 +228,27 @@ pub(super) mod ch4 {
}
pub(super) mod common {
use super::bitfield;
use super::{bitfield, NRx4};
bitfield! {
pub struct FrequencyHigh(u8);
impl Debug;
_initial, _: 7;
_length_disable, _: 6;
_trigger, _: 7;
_length_enable, _set_length_enable: 6;
pub freq_bits, set_freq_bits: 2, 0;
}
impl FrequencyHigh {
pub(crate) fn initial(&self) -> bool {
self._initial()
impl NRx4 for FrequencyHigh {
fn trigger(&self) -> bool {
self._trigger()
}
pub(crate) fn length_disable(&self) -> bool {
self._length_disable()
fn length_enable(&self) -> bool {
self._length_enable()
}
fn set_length_enable(&mut self, value: bool) {
self._set_length_enable(value);
}
}
@@ -402,12 +416,6 @@ pub(super) mod common {
}
}
impl Default for WavePattern {
fn default() -> Self {
Self::OneEighth // Rationale: OneEighth is 0x00
}
}
impl From<WavePattern> for u8 {
fn from(pattern: WavePattern) -> Self {
pattern as Self
@@ -465,12 +473,6 @@ impl Clone for SoundOutput {
}
}
impl Default for SoundOutput {
fn default() -> Self {
Self(0)
}
}
impl From<u8> for SoundOutput {
fn from(byte: u8) -> Self {
Self(byte)
@@ -509,12 +511,6 @@ impl Clone for ChannelControl {
}
}
impl Default for ChannelControl {
fn default() -> Self {
Self(0)
}
}
impl From<u8> for ChannelControl {
fn from(byte: u8) -> Self {
Self(byte)
@@ -557,6 +553,15 @@ 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 {
self.state
}
@@ -567,7 +572,7 @@ pub(super) mod fs {
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum State {
Length,
Nothing,

View File

@@ -7,30 +7,29 @@ use crate::ppu::{Ppu, PpuMode};
use crate::serial::Serial;
use crate::timer::Timer;
use crate::work_ram::{VariableWorkRam, WorkRam};
use std::{fs::File, io::Read};
const BOOT_ROM_SIZE: usize = 0x100;
pub(crate) const BOOT_SIZE: usize = 0x100;
#[derive(Debug)]
pub struct Bus {
boot: Option<[u8; BOOT_ROM_SIZE]>, // Boot ROM is 256b long
cartridge: Option<Cartridge>,
boot: Option<[u8; BOOT_SIZE]>, // Boot ROM is 256b long
pub(crate) cart: Option<Cartridge>,
pub(crate) ppu: Ppu,
work_ram: WorkRam,
var_ram: VariableWorkRam,
pub(crate) timer: Timer,
timer: Timer,
int: Interrupt,
pub(crate) apu: Apu,
high_ram: HighRam,
serial: Serial,
pub(crate) joypad: Joypad,
pub(crate) joyp: Joypad,
}
impl Default for Bus {
fn default() -> Self {
Self {
boot: None,
cartridge: None,
cart: None,
ppu: Default::default(),
work_ram: Default::default(),
var_ram: Default::default(),
@@ -39,31 +38,17 @@ impl Default for Bus {
apu: Default::default(),
high_ram: Default::default(),
serial: Default::default(),
joypad: Default::default(),
joyp: Default::default(),
}
}
}
impl Bus {
pub(crate) fn with_boot(path: &str) -> anyhow::Result<Self> {
let mut file = File::open(path)?;
let mut boot_rom = [0u8; 256];
file.read_exact(&mut boot_rom)?;
Ok(Self {
boot: Some(boot_rom),
pub(crate) fn with_boot(rom: [u8; 256]) -> Self {
Self {
boot: Some(rom),
..Default::default()
})
}
pub(crate) fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> {
self.cartridge = Some(Cartridge::new(path)?);
Ok(())
}
pub(crate) fn rom_title(&self) -> Option<&str> {
self.cartridge.as_ref()?.title()
}
#[allow(dead_code)]
@@ -71,13 +56,19 @@ impl Bus {
self.boot.is_some()
}
#[inline]
pub(crate) fn clock(&mut self) {
self.tick(4);
}
#[inline]
fn tick(&mut self, limit: u8) {
for _ in 0..limit {
self.timer.tick();
if let Some(c) = self.cart.as_mut() {
c.tick()
}
self.ppu.tick();
self.apu.tick(self.timer.divider);
self.dma_tick()
@@ -93,7 +84,7 @@ impl Bus {
}
impl Bus {
pub fn oam_read_byte(&self, addr: u16) -> u8 {
pub(crate) fn oam_read_byte(&self, addr: u16) -> u8 {
match addr {
0x0000..=0x7FFF => {
// 16KB ROM bank 00 (ends at 0x3FFF)
@@ -104,16 +95,16 @@ impl Bus {
}
}
match self.cartridge.as_ref() {
match self.cart.as_ref() {
Some(cart) => cart.read_byte(addr),
None => panic!("Tried to read from a non-existent cartridge"),
None => 0xFF,
}
}
0x8000..=0x9FFF => self.ppu.read_byte(addr), // 8KB Video RAM
0xA000..=0xBFFF => match self.cartridge.as_ref() {
0xA000..=0xBFFF => match self.cart.as_ref() {
// 8KB External RAM
Some(cart) => cart.read_byte(addr),
None => panic!("Tried to read from a non-existent cartridge"),
None => 0xFF,
},
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
@@ -140,7 +131,7 @@ impl Bus {
}
}
pub fn oam_write_byte(&mut self, addr: u16, byte: u8) {
pub(crate) fn oam_write_byte(&mut self, addr: u16, byte: u8) {
self.ppu.oam.write_byte(addr, byte);
}
}
@@ -157,9 +148,9 @@ impl BusIo for Bus {
}
}
match self.cartridge.as_ref() {
match self.cart.as_ref() {
Some(cart) => cart.read_byte(addr),
None => panic!("Tried to read from a non-existent cartridge"),
None => 0xFF,
}
}
0x8000..=0x9FFF => {
@@ -169,10 +160,10 @@ impl BusIo for Bus {
_ => self.ppu.read_byte(addr),
}
}
0xA000..=0xBFFF => match self.cartridge.as_ref() {
0xA000..=0xBFFF => match self.cart.as_ref() {
// 8KB External RAM
Some(cart) => cart.read_byte(addr),
None => panic!("Tried to read from a non-existent cartridge"),
None => 0xFF,
},
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
@@ -219,7 +210,7 @@ impl BusIo for Bus {
// Every address here starts with 0xFF so we can just check the
// low byte to figure out which register it is
match addr & 0x00FF {
0x00 => self.joypad.p1,
0x00 => self.joyp.p1,
0x01 => self.serial.next,
0x02 => self.serial.ctrl.into(),
0x04 => (self.timer.divider >> 8) as u8,
@@ -240,8 +231,9 @@ impl BusIo for Bus {
0x49 => self.ppu.monochrome.obj_palette_1.into(),
0x4A => self.ppu.pos.window_y,
0x4B => self.ppu.pos.window_x,
0x4F => 0xFF, // CGB VRAM Bank Select
_ => {
eprintln!("Read 0xFF from unused IO register {:#06X}.", addr);
tracing::warn!("Attempted read from {:#06X} on IO", addr);
0xFF
}
}
@@ -262,9 +254,8 @@ impl BusIo for Bus {
0x0000..=0x7FFF => {
// 16KB ROM bank 00 (ends at 0x3FFF)
// and 16KB ROM Bank 01 -> NN (switchable via MB)
match self.cartridge.as_mut() {
Some(cart) => cart.write_byte(addr, byte),
None => panic!("Tried to write into non-existent cartridge"),
if let Some(cart) = self.cart.as_mut() {
cart.write_byte(addr, byte);
}
}
0x8000..=0x9FFF => {
@@ -276,9 +267,8 @@ impl BusIo for Bus {
}
0xA000..=0xBFFF => {
// 8KB External RAM
match self.cartridge.as_mut() {
Some(cart) => cart.write_byte(addr, byte),
None => panic!("Tried to write into non-existent cartridge"),
if let Some(cart) = self.cart.as_mut() {
cart.write_byte(addr, byte);
}
}
0xC000..=0xCFFF => self.work_ram.write_byte(addr, byte), // 4KB Work RAM Bank 0
@@ -318,7 +308,7 @@ impl BusIo for Bus {
// Every address here starts with 0xFF so we can just check the
// low byte to figure out which register it is
match addr & 0x00FF {
0x00 => self.joypad.update(byte),
0x00 => self.joyp.update(byte),
0x01 => self.serial.next = byte,
0x02 => self.serial.ctrl = byte.into(),
0x04 => self.timer.divider = 0x0000,
@@ -352,13 +342,15 @@ impl BusIo for Bus {
0x4A => self.ppu.pos.window_y = byte,
0x4B => self.ppu.pos.window_x = byte,
0x4D => {} // CGB Specific Register
0x4F => {} // CGB VRAM Bank Select
0x50 => {
// Disable Boot ROM
if byte != 0 {
self.boot = None;
}
}
_ => eprintln!("Wrote {:#04X} to unused IO register {:#06X}.", byte, addr),
0x70 => {} // CGB WRAM Bank Select
_ => tracing::warn!("Attempted write of {:#04X} to {:#06X} on IO", byte, addr),
};
}
0xFF80..=0xFFFE => {
@@ -380,7 +372,7 @@ impl Bus {
let lcd_stat = self.ppu.int.lcd_stat();
// Read the current interrupt information from the Joypad
let joypad = self.joypad.interrupt();
let joypad = self.joyp.interrupt();
// Read the current interrupt information from the Timer
let timer = self.timer.interrupt();
@@ -410,7 +402,7 @@ impl Bus {
self.ppu.int.set_lcd_stat(lcd_stat);
// Update the Joypad's instance of the following interrupts
self.joypad.set_interrupt(joypad);
self.joyp.set_interrupt(joypad);
// Update the Timer's instance of the following interrupts
self.timer.set_interrupt(timer);

View File

@@ -1,100 +1,117 @@
use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
use bitfield::bitfield;
use crate::bus::BusIo;
use crate::emu::SM83_CLOCK_SPEED;
use crate::Cycle;
const RAM_SIZE_ADDRESS: usize = 0x0149;
const ROM_SIZE_ADDRESS: usize = 0x0148;
const MBC_TYPE_ADDRESS: usize = 0x0147;
const ROM_TITLE_RANGE: std::ops::RangeInclusive<usize> = 0x0134..=0x0143;
const MBC_KIND_ADDRESS: usize = 0x0147;
const ROM_TITLE_START: usize = 0x134;
const ROM_TITLE_MAX_SIZE: usize = 16;
const ROM_MANUFACTURER_START: usize = 0x13F;
#[derive(Debug, Default)]
#[derive(Debug)]
pub(crate) struct Cartridge {
memory: Vec<u8>,
title: Option<String>,
mem: Vec<u8>,
pub(crate) title: Option<String>,
mbc: Box<dyn MBCIo>,
}
impl Cartridge {
pub(crate) fn new<P: AsRef<Path> + ?Sized>(path: &P) -> io::Result<Self> {
let mut memory = vec![];
let mut rom = File::open(path)?;
rom.read_to_end(&mut memory)?;
pub(crate) fn new(mem: Vec<u8>) -> Self {
let title_mem: &[u8; 16] = mem[ROM_TITLE_START..(ROM_TITLE_START + ROM_TITLE_MAX_SIZE)]
.try_into()
.expect("coerce slice containing cartridge title from ROM to [u8; 16]");
let title = Self::find_title(&memory);
eprintln!("Cartridge Title: {:?}", title);
let title = Self::detect_title(title_mem);
let mbc = Self::detect_mbc(&mem);
tracing::info!("Title: {:?}", title);
Ok(Self {
mbc: Self::detect_mbc(&memory),
title,
memory,
})
Self { mem, title, mbc }
}
fn detect_mbc(memory: &[u8]) -> Box<dyn MBCIo> {
let ram_size = Self::detect_ram_info(memory);
let rom_size = Self::detect_rom_info(memory);
let mbc_kind = Self::find_mbc(memory);
pub(crate) fn ext_ram(&self) -> Option<&[u8]> {
self.mbc.ext_ram()
}
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 rom_cap = rom_size.capacity();
eprintln!("Cartridge Ram Size: {} bytes", ram_cap);
eprintln!("Cartridge ROM Size: {} bytes", rom_size.capacity());
eprintln!("MBC Type: {:?}", mbc_kind);
tracing::info!("RAM size: {} bytes", ram_cap);
tracing::info!("ROM size: {} bytes", rom_cap);
tracing::info!("MBC kind: {:?}", mbc_kind);
match mbc_kind {
MBCKind::None => Box::new(NoMBC),
MBCKind::MBC1 => Box::new(MBC1::new(ram_size, rom_size)),
MBCKind::MBC1WithBattery => Box::new(MBC1::new(ram_size, rom_size)), // TODO: Implement Saving
MBCKind::MBC2 => Box::new(MBC2::new(rom_cap)),
MBCKind::MBC2WithBattery => Box::new(MBC2::new(rom_cap)), // TODO: Implement Saving
MBCKind::MBC3 => Box::new(MBC3::new(ram_cap)),
MBCKind::MBC3WithBattery => Box::new(MBC3::new(ram_cap)), // TODO: Implement Saving
MBCKind::MBC5 => Box::new(MBC5::new(ram_cap, rom_cap)),
MBCKind::MBC5WithBattery => Box::new(MBC5::new(ram_cap, rom_cap)), // TDO: Implement Saving
MBCKind::MBC1(hw) => Box::new(MBC1::new(hw, ram_size, rom_size)),
MBCKind::MBC2(hw) => Box::new(MBC2::new(hw, rom_cap)),
MBCKind::MBC3(hw @ MBC3Hardware::RTC) => Box::new(MBC3::new(hw, ram_cap)),
MBCKind::MBC3(hw @ MBC3Hardware::RTCAndBatteryRAM) => Box::new(MBC3::new(hw, ram_cap)),
MBCKind::MBC5(hw @ MBC5Hardware::None) => Box::new(MBC5::new(hw, ram_cap, rom_cap)),
MBCKind::MBC5(hw @ MBC5Hardware::BatteryRAM) => {
Box::new(MBC5::new(hw, ram_cap, rom_cap))
}
kind => todo!("ROMS with {:?} are currently unsupported", kind),
}
}
fn find_title(memory: &[u8]) -> Option<String> {
let slice = &memory[ROM_TITLE_RANGE];
let with_nulls = std::str::from_utf8(slice).ok();
let trimmed = with_nulls.map(|s| s.trim_matches('\0').trim());
fn detect_title(title_mem: &[u8; ROM_TITLE_MAX_SIZE]) -> Option<String> {
const ALT_TITLE_LEN: usize = ROM_MANUFACTURER_START - ROM_TITLE_START;
match trimmed {
// byte slice we have here is purposely not null terminated
let ascii = match title_mem.iter().position(|b| *b == 0x00) {
Some(end) => &title_mem[0..end],
None => &title_mem[0..ROM_TITLE_MAX_SIZE],
};
match std::str::from_utf8(ascii).ok() {
None => match std::str::from_utf8(&title_mem[0..ALT_TITLE_LEN]).ok() {
Some("") | None => None,
Some(_) => trimmed.map(String::from),
Some(title) => Some(String::from(title.trim())),
},
Some("") => None,
Some(title) => Some(String::from(title.trim())),
}
}
pub(crate) fn title(&self) -> Option<&str> {
self.title.as_deref()
}
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 {
fn detect_mbc_kind(id: u8) -> MBCKind {
use MBCKind::*;
match memory[MBC_TYPE_ADDRESS] {
match id {
0x00 => None,
0x01 | 0x02 => MBC1,
0x03 => MBC1WithBattery,
0x05 => MBC2,
0x06 => MBC2WithBattery,
0x19 | 0x1A => MBC5,
0x1B => MBC5WithBattery,
0x13 => MBC3WithBattery,
0x11 | 0x12 => MBC3,
id => unimplemented!("id {:#04X} is an unsupported MBC", id),
0x01 => MBC1(MBC1Hardware::None),
0x02 => MBC1(MBC1Hardware::RAM),
0x03 => MBC1(MBC1Hardware::BatteryRAM),
0x05 => MBC2(MBC2Hardware::None),
0x06 => MBC2(MBC2Hardware::BatteryRAM),
0x08 | 0x09 => unimplemented!("NoMBC + RAM and NoMBC + Battery unsupported"),
0x0B | 0x0C | 0x0D => unimplemented!("MM01 unsupported"),
0x0F => MBC3(MBC3Hardware::RTC),
0x10 => MBC3(MBC3Hardware::RTCAndBatteryRAM),
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),
}
}
}
@@ -104,8 +121,8 @@ impl BusIo for Cartridge {
use MBCResult::*;
match self.mbc.handle_read(addr) {
Address(addr) => self.memory[addr],
Value(byte) => byte,
Addr(addr) => self.mem[addr],
Byte(byte) => byte,
}
}
@@ -122,21 +139,24 @@ struct MBC1 {
ram_bank: u8,
mode: bool,
ram_size: RamSize,
memory: Vec<u8>,
mem: Vec<u8>,
rom_size: RomSize,
mem_enabled: bool,
hw: MBC1Hardware,
}
impl MBC1 {
fn new(ram_size: RamSize, rom_size: RomSize) -> Self {
fn new(hw: MBC1Hardware, ram_size: RamSize, rom_size: RomSize) -> Self {
Self {
rom_bank: 0x01,
memory: vec![0; ram_size.capacity() as usize],
mem: vec![0; ram_size.capacity() as usize],
ram_size,
rom_size,
ram_bank: Default::default(),
mode: Default::default(),
mem_enabled: Default::default(),
hw,
}
}
@@ -205,22 +225,34 @@ impl MBC1 {
}
}
impl Savable for MBC1 {
fn ext_ram(&self) -> Option<&[u8]> {
match self.hw {
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 {
0x0000..=0x3FFF if self.mode => {
Address(0x4000 * self.zero_bank() as usize + addr as usize)
Addr(0x4000 * self.zero_bank() as usize + addr as usize)
}
0x0000..=0x3FFF => Address(addr as usize),
0x4000..=0x7FFF => {
Address(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000))
}
0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => {
Value(self.memory[self.ram_addr(addr)])
}
0xA000..=0xBFFF => Value(0xFF),
0x0000..=0x3FFF => Addr(addr as usize),
0x4000..=0x7FFF => Addr(0x4000 * self.high_bank() as usize + (addr as usize - 0x4000)),
0xA000..=0xBFFF if self.mem_enabled => Byte(self.mem[self.ram_addr(addr)]),
0xA000..=0xBFFF => Byte(0xFF),
_ => unreachable!("A read from {:#06X} should not be handled by MBC1", addr),
}
}
@@ -235,9 +267,9 @@ impl MBCIo for MBC1 {
}
0x4000..=0x5FFF => self.ram_bank = byte & 0x03,
0x6000..=0x7FFF => self.mode = (byte & 0x01) == 0x01,
0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => {
0xA000..=0xBFFF if self.mem_enabled => {
let ram_addr = self.ram_addr(addr);
self.memory[ram_addr] = byte;
self.mem[ram_addr] = byte;
}
0xA000..=0xBFFF => {} // Ram isn't enabled, ignored write
_ => unreachable!("A write to {:#06X} should not be handled by MBC1", addr),
@@ -245,10 +277,120 @@ impl MBCIo for MBC1 {
}
}
impl RtClockTick 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 RtClockTick 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 RtClockTick {
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)]
enum MBC3Device {
ExternalRam,
RealTimeClock,
Clock(RtcRegister),
}
#[derive(Debug, Clone, Copy)]
enum RtcRegister {
Second,
Minute,
Hour,
DayLow,
DayHigh,
}
#[derive(Debug)]
@@ -260,21 +402,44 @@ struct MBC3 {
devs_enabled: bool,
mapped: Option<MBC3Device>,
memory: Vec<u8>,
mem: Vec<u8>,
// RTC Data Latch Previous Write
prev_latch_write: Option<u8>,
hw: MBC3Hardware,
rtc: RtClock,
rtc_latch: Option<RtClock>,
}
impl MBC3 {
fn new(ram_cap: usize) -> Self {
fn new(hw: MBC3Hardware, ram_cap: usize) -> Self {
Self {
memory: vec![0; ram_cap],
mem: vec![0; ram_cap],
rom_bank: Default::default(),
ram_bank: Default::default(),
devs_enabled: Default::default(),
mapped: Default::default(),
prev_latch_write: Default::default(),
rtc: Default::default(),
rtc_latch: Default::default(),
hw,
}
}
}
impl Savable for MBC3 {
fn ext_ram(&self) -> Option<&[u8]> {
match self.hw {
MBC3Hardware::BatteryRAM | MBC3Hardware::RTCAndBatteryRAM => Some(&self.mem),
_ => None,
}
}
fn ext_ram_mut(&mut self) -> Option<&mut [u8]> {
match self.hw {
MBC3Hardware::BatteryRAM | MBC3Hardware::RTCAndBatteryRAM => Some(&mut self.mem),
_ => None,
}
}
}
@@ -282,18 +447,28 @@ impl MBC3 {
impl MBCIo for MBC3 {
fn handle_read(&self, addr: u16) -> MBCResult {
use MBCResult::*;
use RtcRegister::*;
let res = match addr {
0x0000..=0x3FFF => Address(addr as usize),
0x4000..=0x7FFF => Address(0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)),
0x0000..=0x3FFF => Addr(addr as usize),
0x4000..=0x7FFF => Addr(0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)),
0xA000..=0xBFFF => match self.mapped {
Some(MBC3Device::ExternalRam) if self.devs_enabled => {
Value(self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
Byte(self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
}
Some(MBC3Device::RealTimeClock) if self.devs_enabled => {
todo!("Return Latched value of register")
}
_ => Value(0xFF),
Some(MBC3Device::Clock(reg)) if self.devs_enabled => Byte(
self.rtc_latch
.as_ref()
.map(|rtc| match reg {
Second => rtc.sec,
Minute => rtc.min,
Hour => 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),
};
@@ -302,6 +477,8 @@ impl MBCIo for MBC3 {
}
fn handle_write(&mut self, addr: u16, byte: u8) {
use RtcRegister::*;
match addr {
0x000..=0x1FFF => self.devs_enabled = (byte & 0x0F) == 0x0A, // Enable External RAM and Access to RTC if there is one
0x2000..=0x3FFF => {
@@ -315,26 +492,37 @@ impl MBCIo for MBC3 {
self.ram_bank = byte & 0x03;
self.mapped = Some(MBC3Device::ExternalRam);
}
0x08 | 0x09 | 0x0A | 0x0B | 0x0C => {
self.mapped = Some(MBC3Device::RealTimeClock);
}
0x08 => self.mapped = Some(MBC3Device::Clock(Second)),
0x09 => self.mapped = Some(MBC3Device::Clock(Minute)),
0x0A => self.mapped = Some(MBC3Device::Clock(Hour)),
0x0B => self.mapped = Some(MBC3Device::Clock(DayLow)),
0x0C => self.mapped = Some(MBC3Device::Clock(DayHigh)),
_ => {}
},
0x6000..=0x7FFF => {
if let Some(0x00) = self.prev_latch_write {
if byte == 0x01 {
todo!("Perform Data Latch")
self.rtc_latch = Some(self.rtc);
}
}
self.prev_latch_write = Some(byte);
}
0xA000..=0xBFFF => match self.mapped {
Some(MBC3Device::ExternalRam) if self.devs_enabled => {
self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte
self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte
}
Some(MBC3Device::RealTimeClock) if self.devs_enabled => {
todo!("Write to RTC")
Some(MBC3Device::Clock(rtc_reg)) if self.devs_enabled => match rtc_reg {
Second => {
self.rtc.sec = byte & 0x3F;
// Writing to RTC S resets the internal sub-second counter
self.rtc.cycles = 0;
}
Minute => self.rtc.min = byte & 0x3F,
Hour => 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),
@@ -342,6 +530,14 @@ impl MBCIo for MBC3 {
}
}
impl RtClockTick for MBC3 {
fn tick(&mut self) {
if let MBC3Hardware::RTCAndBatteryRAM | MBC3Hardware::RTC = self.hw {
self.rtc.tick();
}
}
}
#[derive(Debug)]
struct MBC5 {
/// 9-bit number
@@ -350,19 +546,21 @@ struct MBC5 {
ram_bank: u8,
rom_cap: usize,
memory: Vec<u8>,
mem: Vec<u8>,
mem_enabled: bool,
hw: MBC5Hardware,
}
impl MBC5 {
fn new(ram_cap: usize, rom_cap: usize) -> Self {
fn new(hw: MBC5Hardware, ram_cap: usize, rom_cap: usize) -> Self {
Self {
rom_bank: 0x01,
memory: vec![0; ram_cap],
mem: vec![0; ram_cap],
rom_cap,
ram_bank: Default::default(),
mem_enabled: Default::default(),
hw,
}
}
@@ -371,17 +569,33 @@ impl MBC5 {
}
}
impl Savable for MBC5 {
fn ext_ram(&self) -> Option<&[u8]> {
match self.hw {
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 {
0x0000..=0x3FFF => Address(addr as usize),
0x4000..=0x7FFF => Address(self.bank_addr(addr)),
0x0000..=0x3FFF => Addr(addr as usize),
0x4000..=0x7FFF => Addr(self.bank_addr(addr)),
0xA000..=0xBFFF if self.mem_enabled => {
Value(self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
Byte(self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
}
0xA000..=0xBFFF => Value(0xFF),
0xA000..=0xBFFF => Byte(0xFF),
_ => unreachable!("A read from {:#06X} should not be handled by MBC5", addr),
}
}
@@ -393,7 +607,7 @@ impl MBCIo for MBC5 {
0x3000..=0x3FFF => self.rom_bank = (self.rom_bank & 0x00FF) | (byte as u16 & 0x01) << 8,
0x4000..=0x5FFF => self.ram_bank = byte & 0x0F,
0xA000..=0xBFFF if self.mem_enabled => {
self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte;
self.mem[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte;
}
0xA000..=0xBFFF => {}
_ => unreachable!("A write to {:#06X} should not be handled by MBC5", addr),
@@ -401,25 +615,31 @@ impl MBCIo for MBC5 {
}
}
impl RtClockTick for MBC5 {
fn tick(&mut self) {}
}
#[derive(Debug)]
struct MBC2 {
/// 4-bit number
rom_bank: u8,
memory: Box<[u8; Self::RAM_SIZE]>,
mem_enabled: bool,
mem: Box<[u8; Self::RAM_SIZE]>,
mem_enabled: bool,
rom_cap: usize,
hw: MBC2Hardware,
}
impl MBC2 {
const RAM_SIZE: usize = 0x0200;
fn new(rom_cap: usize) -> Self {
fn new(hw: MBC2Hardware, rom_cap: usize) -> Self {
Self {
rom_bank: 0x01,
memory: Box::new([0; Self::RAM_SIZE]),
mem_enabled: Default::default(),
mem: Box::new([0; Self::RAM_SIZE]),
rom_cap,
mem_enabled: Default::default(),
hw,
}
}
@@ -428,18 +648,34 @@ impl MBC2 {
}
}
impl Savable for MBC2 {
fn ext_ram(&self) -> Option<&[u8]> {
match self.hw {
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 {
0x0000..=0x3FFF => Address(addr as usize),
0x4000..=0x7FFF => Address(self.rom_addr(addr)),
0x0000..=0x3FFF => Addr(addr as usize),
0x4000..=0x7FFF => Addr(self.rom_addr(addr)),
0xA000..=0xBFFF if self.mem_enabled => {
let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1);
Value(self.memory[mbc2_addr] | 0xF0)
Byte(self.mem[mbc2_addr] | 0xF0)
}
0xA000..=0xBFFF => Value(0xFF),
0xA000..=0xBFFF => Byte(0xFF),
_ => unreachable!("A read from {:#06X} should not be handled by MBC2", addr),
}
}
@@ -454,7 +690,7 @@ impl MBCIo for MBC2 {
0x0000..=0x3FFF => self.mem_enabled = nybble == 0x0A,
0xA000..=0xBFFF if self.mem_enabled => {
let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1);
self.memory[mbc2_addr] = nybble;
self.mem[mbc2_addr] = nybble;
}
0x4000..=0x7FFF | 0xA000..=0xBFFF => {}
_ => unreachable!("A write to {:#06X} should not be handled by MBC2", addr),
@@ -462,47 +698,87 @@ impl MBCIo for MBC2 {
}
}
impl RtClockTick for MBC2 {
fn tick(&mut self) {}
}
#[derive(Debug)]
struct NoMBC;
impl Savable for NoMBC {
fn ext_ram(&self) -> Option<&[u8]> {
None
}
fn ext_ram_mut(&mut self) -> Option<&mut [u8]> {
None
}
}
impl MBCIo for NoMBC {
fn handle_read(&self, addr: u16) -> MBCResult {
MBCResult::Address(addr as usize)
MBCResult::Addr(addr as usize)
}
fn handle_write(&mut self, _addr: u16, _byte: u8) {
// eprintln!("Tried to write {:#04X} to a read-only cartridge", byte);
fn handle_write(&mut self, _: u16, byte: u8) {
tracing::warn!("Attempted write of {:#04X} to cartridge w/out MBC", byte);
}
}
trait MBCIo {
impl RtClockTick for NoMBC {
fn tick(&mut self) {}
}
trait MBCIo: Savable + RtClockTick {
fn handle_read(&self, addr: u16) -> MBCResult;
fn handle_write(&mut self, addr: u16, byte: u8);
}
#[derive(Debug, Clone, Copy)]
enum MBCResult {
Address(usize),
Value(u8),
Addr(usize),
Byte(u8),
}
#[derive(Debug, Clone, Copy)]
enum MBCKind {
None,
MBC1,
MBC1WithBattery,
MBC2,
MBC2WithBattery,
MBC3,
MBC3WithBattery,
MBC5,
MBC5WithBattery,
MBC1(MBC1Hardware),
MBC2(MBC2Hardware),
MBC3(MBC3Hardware),
MBC5(MBC5Hardware),
}
impl Default for MBCKind {
fn default() -> Self {
Self::None
#[derive(Debug, Clone, Copy)]
enum MBC1Hardware {
None,
RAM,
BatteryRAM,
}
#[derive(Debug, Clone, Copy)]
enum MBC2Hardware {
None,
BatteryRAM,
}
#[derive(Debug, Clone, Copy)]
enum MBC3Hardware {
RTC,
RTCAndBatteryRAM,
None,
RAM,
BatteryRAM,
}
#[derive(Debug, Clone, Copy)]
enum MBC5Hardware {
None,
RAM,
BatteryRAM,
Rumble,
RumbleRAM,
RumbleBatteryRAM,
}
#[derive(Debug, Clone, Copy, PartialEq)]
@@ -530,12 +806,6 @@ impl RamSize {
}
}
impl Default for RamSize {
fn default() -> Self {
Self::None
}
}
impl From<u8> for RamSize {
fn from(byte: u8) -> Self {
use RamSize::*;
@@ -610,8 +880,74 @@ impl std::fmt::Debug for Box<dyn MBCIo> {
}
}
impl Default for Box<dyn MBCIo> {
fn default() -> Self {
Box::new(NoMBC)
trait Savable {
fn ext_ram(&self) -> Option<&[u8]>;
fn ext_ram_mut(&mut self) -> Option<&mut [u8]>;
}
#[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)
);
}
}

View File

@@ -1,16 +1,13 @@
use crate::apu::Apu;
use crate::bus::{Bus, BusIo};
use crate::bus::{Bus, BusIo, BOOT_SIZE};
use crate::instruction::Instruction;
use crate::interrupt::{InterruptEnable, InterruptFlag};
use crate::joypad::Joypad;
use crate::ppu::Ppu;
use crate::Cycle;
use bitfield::bitfield;
use std::fmt::{Display, Formatter, Result as FmtResult};
#[derive(Debug, Default)]
#[derive(Debug)]
pub struct Cpu {
pub bus: Bus,
pub(crate) bus: Bus,
reg: Registers,
flags: Flags,
ime: ImeState,
@@ -18,31 +15,16 @@ pub struct Cpu {
}
impl Cpu {
pub fn new() -> Self {
pub(crate) fn new(rom: [u8; BOOT_SIZE]) -> 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()
bus: Bus::with_boot(rom),
reg: Default::default(),
flags: Flags(0),
ime: ImeState::Disabled,
state: State::Execute,
}
}
pub fn boot_new(path: &str) -> anyhow::Result<Self> {
Ok(Self {
bus: Bus::with_boot(path)?,
..Default::default()
})
}
pub(crate) fn ime(&self) -> ImeState {
self.ime
}
@@ -60,10 +42,7 @@ impl Cpu {
}
pub(crate) fn is_halted(&self) -> bool {
match self.state {
State::Halt(_) => true,
_ => false,
}
matches!(self.state, State::Halt(_))
}
pub(crate) fn halt_kind(&self) -> Option<HaltKind> {
@@ -72,13 +51,11 @@ impl Cpu {
_ => None,
}
}
pub fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> {
self.bus.load_cartridge(path)
}
pub fn rom_title(&self) -> Option<&str> {
self.bus.rom_title()
impl Default for Cpu {
fn default() -> Self {
Cpu::new(*include_bytes!("../bin/bootix_dmg.bin"))
}
}
@@ -116,7 +93,7 @@ impl Cpu {
/// routine.
///
/// Handle HALT and interrupts.
pub fn step(&mut self) -> Cycle {
pub(crate) fn step(&mut self) -> Cycle {
// Log instructions
// if self.reg.pc > 0xFF {
// let out = std::io::stdout();
@@ -131,13 +108,10 @@ impl Cpu {
use HaltKind::*;
self.bus.clock();
let elapsed = match kind {
return match kind {
ImeEnabled | NonePending => 4,
SomePending => todo!("Implement HALT bug"),
};
return elapsed;
}
let opcode = self.fetch();
@@ -146,11 +120,11 @@ impl Cpu {
self.handle_ei();
// For use in Blargg's Test ROMs
if self.read_byte(0xFF02) == 0x81 {
let c = self.read_byte(0xFF01) as char;
self.write_byte(0xFF02, 0x00);
eprint!("{}", c);
}
// if self.read_byte(0xFF02) == 0x81 {
// let c = self.read_byte(0xFF01) as char;
// self.write_byte(0xFF02, 0x00);
// eprint!("{}", c);
// }
elapsed
}
@@ -167,18 +141,6 @@ impl BusIo for Cpu {
}
impl Cpu {
pub fn ppu(&mut self) -> &Ppu {
&self.bus.ppu
}
pub fn apu_mut(&mut self) -> &mut Apu {
&mut self.bus.apu
}
pub(crate) fn joypad_mut(&mut self) -> &mut Joypad {
&mut self.bus.joypad
}
fn handle_ei(&mut self) {
match self.ime {
ImeState::EiExecuted => self.ime = ImeState::Pending,
@@ -276,12 +238,6 @@ enum State {
// Stop,
}
impl Default for State {
fn default() -> Self {
Self::Execute
}
}
impl Cpu {
pub(crate) fn set_register(&mut self, register: Register, value: u8) {
use Register::*;
@@ -475,12 +431,6 @@ impl Clone for Flags {
}
}
impl Default for Flags {
fn default() -> Self {
Self(0)
}
}
impl Display for Flags {
fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
if self.z() {
@@ -535,9 +485,3 @@ pub(crate) enum ImeState {
Pending,
Enabled,
}
impl Default for ImeState {
fn default() -> Self {
Self::Disabled
}
}

View File

@@ -1,67 +1,159 @@
use crate::cpu::Cpu as SM83;
use crate::joypad;
use crate::ppu::Ppu;
use crate::Cycle;
use anyhow::Result;
use crate::apu::gen::SampleProducer;
use crate::bus::BOOT_SIZE;
use crate::cartridge::Cartridge;
use crate::cpu::Cpu;
use crate::{Cycle, GB_HEIGHT, GB_WIDTH};
use clap::crate_name;
use gilrs::Gilrs;
use std::fs::File;
use std::io::{Read, Write};
use std::path::{Path, PathBuf};
use std::time::Duration;
use winit_input_helper::WinitInputHelper;
use thiserror::Error;
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 CYCLES_IN_FRAME: Cycle = 456 * 154; // 456 Cycles times 154 scanlines
pub(crate) const SM83_CLOCK_SPEED: u64 = 0x40_0000; // Hz which is 4.194304Mhz
const DEFAULT_TITLE: &str = "DMG-01 Emulator";
const DEFAULT_TITLE: &str = "Game Boy Screen";
pub fn init(boot_path: Option<&str>, rom_path: &str) -> Result<SM83> {
let mut cpu = match boot_path {
Some(path) => SM83::boot_new(path)?,
None => SM83::new(),
};
eprintln!("Initialized GB Emulator");
cpu.load_cartridge(rom_path)?;
Ok(cpu)
}
pub fn rom_title(game_boy: &SM83) -> &str {
game_boy.rom_title().unwrap_or(DEFAULT_TITLE)
}
pub fn run(
game_boy: &mut SM83,
gamepad: &mut Gilrs,
input: &WinitInputHelper,
target: Cycle,
) -> Cycle {
pub fn run_frame(cpu: &mut Cpu, gamepad: &mut Gilrs, key: KeyboardInput) -> Cycle {
let mut elapsed = 0;
if let Some(event) = gamepad.next_event() {
joypad::handle_gamepad_input(game_boy.joypad_mut(), event);
crate::joypad::handle_gamepad_input(&mut cpu.bus.joyp, event);
}
joypad::handle_keyboard_input(game_boy.joypad_mut(), input);
crate::joypad::handle_keyboard_input(&mut cpu.bus.joyp, key);
while elapsed < target {
elapsed += game_boy.step();
}
elapsed
}
pub fn run_frame(game_boy: &mut SM83, gamepad: &mut Gilrs, input: &WinitInputHelper) -> Cycle {
let mut elapsed = 0;
if let Some(event) = gamepad.next_event() {
joypad::handle_gamepad_input(game_boy.joypad_mut(), event);
}
joypad::handle_keyboard_input(game_boy.joypad_mut(), input);
while elapsed < CYCLES_IN_FRAME {
elapsed += game_boy.step();
elapsed += cpu.step();
}
elapsed
}
pub fn draw(ppu: &Ppu, frame: &mut [u8]) {
ppu.copy_to_gui(frame);
pub fn save_and_exit(cpu: &Cpu, control_flow: &mut ControlFlow) {
write_save(cpu);
*control_flow = ControlFlow::Exit;
}
#[inline]
pub fn pixel_buf(cpu: &Cpu) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] {
cpu.bus.ppu.frame_buf.as_ref()
}
pub fn from_boot_rom<P: AsRef<Path>>(path: P) -> std::io::Result<Cpu> {
Ok(Cpu::new(read_boot(path)?))
}
pub fn read_game_rom<P: AsRef<Path>>(cpu: &mut Cpu, path: P) -> std::io::Result<()> {
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()
.map(|c| c.title.as_deref())
.flatten()
.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 read_save_from_file(cart: &mut Cartridge) -> Result<PathBuf, SaveError> {
match cart.title.clone().zip(cart.ext_ram_mut()) {
Some((title, ext_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::open(&save_path)?;
let mut memory = Vec::new();
file.read_to_end(&mut memory)?;
if ext_ram.len() != memory.len() {
return Err(SaveError::DiffSize);
}
ext_ram.copy_from_slice(&memory);
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,
}

239
src/gui.rs Normal file
View File

@@ -0,0 +1,239 @@
use egui::{ClippedMesh, CtxRef, TextureId};
use egui_wgpu_backend::{BackendError, RenderPass, ScreenDescriptor};
use egui_winit_platform::Platform;
use wgpu::{
Adapter, CommandEncoder, Device, Extent3d, FilterMode, Instance, Queue, RequestDeviceError,
Surface, SurfaceConfiguration, SurfaceTexture, Texture, TextureFormat, TextureUsages,
TextureView,
};
use winit::error::OsError;
use winit::event::{ElementState, KeyboardInput};
use winit::event_loop::EventLoop;
use winit::window::Window;
use crate::{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;
/// Holds GUI State
#[derive(Debug, Clone)]
pub struct GuiState {
/// When true, egui winit should exit the application
pub quit: bool,
pub title: String,
}
impl GuiState {
pub fn new(title: String) -> Self {
Self {
title,
quit: Default::default(),
}
}
}
/// 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
}
}
pub fn build_window<T>(event_loop: &EventLoop<T>) -> Result<Window, OsError> {
use winit::dpi::PhysicalSize;
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)
}
pub fn create_surface(window: &Window) -> (Instance, Surface) {
use wgpu::Backends;
let instance = Instance::new(Backends::PRIMARY);
let surface = unsafe { instance.create_surface(window) };
(instance, surface)
}
pub fn request_adapter(instance: &Instance, surface: &Surface) -> Option<Adapter> {
use wgpu::{PowerPreference, RequestAdapterOptions};
pollster::block_on(instance.request_adapter(&RequestAdapterOptions {
power_preference: PowerPreference::HighPerformance,
force_fallback_adapter: false, // TODO: What do I want to do with this?
compatible_surface: Some(surface),
}))
}
pub fn request_device(adapter: &Adapter) -> Result<(Device, Queue), RequestDeviceError> {
use wgpu::{DeviceDescriptor, Features, Limits};
pollster::block_on(adapter.request_device(
&DeviceDescriptor {
label: None,
features: Features::default(),
limits: Limits::default(),
},
None,
))
}
pub fn surface_config(window: &Window, format: TextureFormat) -> SurfaceConfiguration {
use wgpu::PresentMode;
let size = window.inner_size();
SurfaceConfiguration {
usage: TextureUsages::RENDER_ATTACHMENT,
format,
width: size.width as u32,
height: size.height as u32,
present_mode: PresentMode::Mailbox,
}
}
pub fn platform_desc(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()
})
}
pub fn texture_size() -> Extent3d {
Extent3d {
width: GB_WIDTH as u32,
height: GB_HEIGHT as u32,
..Default::default()
}
}
pub fn create_texture(device: &Device, size: Extent3d) -> Texture {
use wgpu::{TextureDescriptor, TextureDimension};
device.create_texture(&TextureDescriptor {
size,
mip_level_count: 1,
sample_count: 1,
dimension: TextureDimension::D2,
format: TextureFormat::Rgba8UnormSrgb,
usage: TextureUsages::COPY_DST | TextureUsages::TEXTURE_BINDING,
label: Some("gb_pixel_buffer"),
})
}
#[inline]
pub 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,
);
}
pub fn expose_texture_to_egui(
render_pass: &mut RenderPass,
device: &Device,
texture: &Texture,
) -> TextureId {
render_pass.egui_texture_from_wgpu_texture(device, texture, FILTER_MODE)
}
#[inline]
pub fn create_view(frame: &SurfaceTexture) -> TextureView {
use wgpu::TextureViewDescriptor;
frame.texture.create_view(&TextureViewDescriptor::default())
}
#[inline]
pub fn create_command_encoder(device: &Device) -> CommandEncoder {
use wgpu::CommandEncoderDescriptor;
device.create_command_encoder(&CommandEncoderDescriptor {
label: Some("encoder"),
})
}
#[inline]
pub 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,
}
}
#[inline]
pub fn execute_render_pass(
render_pass: &mut RenderPass,
encoder: &mut CommandEncoder,
view: &TextureView,
jobs: Vec<ClippedMesh>,
descriptor: &ScreenDescriptor,
) -> Result<(), BackendError> {
render_pass.execute(encoder, view, &jobs, descriptor, Some(wgpu::Color::BLACK))
}
#[inline]
pub fn draw_egui(app: &mut GuiState, ctx: &CtxRef, texture_id: TextureId) {
egui::TopBottomPanel::top("top_panel").show(ctx, |ui| {
egui::menu::menu(ui, "File", |ui| {
if ui.button("Quit").clicked() {
app.quit = true;
}
});
egui::Window::new(&app.title).show(ctx, |ui| {
ui.image(
texture_id,
[GB_WIDTH as f32 * SCALE, GB_HEIGHT as f32 * SCALE],
);
})
});
}

View File

@@ -17,11 +17,11 @@ impl Default for HighRam {
}
impl BusIo for HighRam {
fn write_byte(&mut self, addr: u16, byte: u8) {
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]
}
fn write_byte(&mut self, addr: u16, byte: u8) {
self.buf[addr as usize - HIGH_RAM_START_ADDRESS] = byte;
}
}

View File

@@ -116,7 +116,7 @@ impl std::fmt::Debug for Instruction {
impl Instruction {
pub(crate) fn execute(cpu: &mut Cpu, instruction: Self) -> Cycle {
match instruction {
Instruction::NOP => (4),
Instruction::NOP => 4,
Instruction::LD(target, src) => match (target, src) {
(LDTarget::IndirectImmediateWord, LDSource::SP) => {
// LD (u16), SP | Store stack pointer in byte at 16-bit register
@@ -334,7 +334,6 @@ impl Instruction {
Instruction::ADD(target, src) => match (target, src) {
(AddTarget::HL, AddSource::Group1(pair)) => {
// ADD HL, r16 | Add 16-bit register to HL
// FIXME: Memory Timings are not properly emulated for this instruction
use Group1RegisterPair::*;
let mut flags: Flags = *cpu.flags();
@@ -342,10 +341,11 @@ impl Instruction {
BC | DE | HL | SP => {
let left = cpu.register_pair(RegisterPair::HL);
let right = cpu.register_pair(pair.as_register_pair());
cpu.set_register_pair(
RegisterPair::HL,
Self::add_u16(left, right, &mut flags),
);
let result = Self::add_u16(left, right, &mut flags);
cpu.set_register(CpuRegister::L, result as u8);
cpu.bus.clock();
cpu.set_register(CpuRegister::H, (result >> 8) as u8);
}
}
cpu.set_flags(flags);
@@ -361,12 +361,12 @@ impl Instruction {
let (cycles, sum) = match reg {
B | C | D | E | H | L | A => {
let right = cpu.register(reg.cpu_register());
((4), Self::add(left, right, &mut flags))
(4, Self::add(left, right, &mut flags))
}
IndirectHL => {
let addr = cpu.register_pair(RegisterPair::HL);
let right = Self::read_byte(&mut cpu.bus, addr);
((8), Self::add(left, right, &mut flags))
(8, Self::add(left, right, &mut flags))
}
};
@@ -376,12 +376,15 @@ impl Instruction {
}
(AddTarget::SP, AddSource::ImmediateSignedByte) => {
// 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 left = cpu.register_pair(RegisterPair::SP);
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.bus.clock();
cpu.set_flags(flags);
16
}
@@ -423,7 +426,6 @@ impl Instruction {
AllRegisters::Group1(pair) => {
// INC r16 | Increment 16-bit register
// Note: No flags are set with this version of the INC instruction
// FIXME: Memory Timings are not properly emulated for this instruction
use Group1RegisterPair::*;
match pair {
@@ -431,6 +433,7 @@ impl Instruction {
let pair = pair.as_register_pair();
let left = cpu.register_pair(pair);
cpu.set_register_pair(pair, left.wrapping_add(1));
cpu.bus.clock(); // internal
}
}
8
@@ -462,7 +465,6 @@ impl Instruction {
}
AllRegisters::Group1(pair) => {
// DEC r16 | Decrement Register Pair
// FIXME: Memory Timings are not properly emulated for this instruction
use Group1RegisterPair::*;
match pair {
@@ -470,6 +472,7 @@ impl Instruction {
let pair = pair.as_register_pair();
let left = cpu.register_pair(pair);
cpu.set_register_pair(pair, left.wrapping_sub(1));
cpu.bus.clock(); // internal
}
};
8
@@ -610,13 +613,13 @@ impl Instruction {
B | C | D | E | H | L | A => {
let right = cpu.register(reg.cpu_register());
let sum = Self::add_with_carry_bit(left, right, flags.c(), &mut flags);
((4), sum)
(4, sum)
}
IndirectHL => {
let addr = cpu.register_pair(RegisterPair::HL);
let right = Self::read_byte(&mut cpu.bus, addr);
let sum = Self::add_with_carry_bit(left, right, flags.c(), &mut flags);
((8), sum)
(8, sum)
}
};
cpu.set_register(CpuRegister::A, sum);
@@ -646,12 +649,12 @@ impl Instruction {
let (cycles, diff) = match reg {
B | C | D | E | H | L | A => {
let right = cpu.register(reg.cpu_register());
((4), Self::sub(left, right, &mut flags))
(4, Self::sub(left, right, &mut flags))
}
IndirectHL => {
let addr = cpu.register_pair(RegisterPair::HL);
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);
@@ -681,13 +684,13 @@ impl Instruction {
B | C | D | E | H | L | A => {
let right = cpu.register(reg.cpu_register());
let diff = Self::sub_with_carry(left, right, flags.c(), &mut flags);
((4), diff)
(4, diff)
}
IndirectHL => {
let addr = cpu.register_pair(RegisterPair::HL);
let right = Self::read_byte(&mut cpu.bus, addr);
let diff = Self::sub_with_carry(left, right, flags.c(), &mut flags);
((8), diff)
(8, diff)
}
};
cpu.set_register(CpuRegister::A, diff);
@@ -717,7 +720,7 @@ impl Instruction {
IndirectHL => {
let addr = cpu.register_pair(RegisterPair::HL);
let right = Self::read_byte(&mut cpu.bus, addr);
((8), left & right)
(8, left & right)
}
};
cpu.set_register(CpuRegister::A, acc);
@@ -743,7 +746,7 @@ impl Instruction {
IndirectHL => {
let addr = cpu.register_pair(RegisterPair::HL);
let right = Self::read_byte(&mut cpu.bus, addr);
((8), left ^ right)
(8, left ^ right)
}
};
cpu.set_register(CpuRegister::A, acc);
@@ -769,7 +772,7 @@ impl Instruction {
IndirectHL => {
let addr = cpu.register_pair(RegisterPair::HL);
let right = Self::read_byte(&mut cpu.bus, addr);
((8), left | right)
(8, left | right)
}
};
cpu.set_register(CpuRegister::A, acc);
@@ -1053,14 +1056,14 @@ impl Instruction {
let byte = cpu.register(reg);
let rotated = byte.rotate_left(1);
cpu.set_register(reg, rotated);
((8), byte >> 7, rotated)
(8, byte >> 7, rotated)
}
IndirectHL => {
let addr = cpu.register_pair(RegisterPair::HL);
let byte = Self::read_byte(&mut cpu.bus, addr);
let rotated = byte.rotate_left(1);
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);
@@ -1076,14 +1079,14 @@ impl Instruction {
let byte = cpu.register(reg);
let rotated = byte.rotate_right(1);
cpu.set_register(reg, rotated);
((8), byte & 0x01, rotated)
(8, byte & 0x01, rotated)
}
IndirectHL => {
let addr = cpu.register_pair(RegisterPair::HL);
let byte = Self::read_byte(&mut cpu.bus, addr);
let rotated = byte.rotate_right(1);
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);
@@ -1101,14 +1104,14 @@ impl Instruction {
let byte = cpu.register(reg);
let (rotated, carry) = Self::rl_thru_carry(byte, flags.c());
cpu.set_register(reg, rotated);
((8), rotated, carry)
(8, rotated, carry)
}
IndirectHL => {
let addr = cpu.register_pair(RegisterPair::HL);
let byte = Self::read_byte(&mut cpu.bus, addr);
let (rotated, carry) = Self::rl_thru_carry(byte, flags.c());
Self::write_byte(&mut cpu.bus, addr, rotated);
((16), rotated, carry)
(16, rotated, carry)
}
};
cpu.update_flags(rotated == 0, false, false, carry);
@@ -1126,14 +1129,14 @@ impl Instruction {
let byte = cpu.register(reg);
let (rotated, carry) = Self::rr_thru_carry(byte, flags.c());
cpu.set_register(reg, rotated);
((8), rotated, carry)
(8, rotated, carry)
}
IndirectHL => {
let addr = cpu.register_pair(RegisterPair::HL);
let byte = Self::read_byte(&mut cpu.bus, addr);
let (rotated, carry) = Self::rr_thru_carry(byte, flags.c());
Self::write_byte(&mut cpu.bus, addr, rotated);
((16), rotated, carry)
(16, rotated, carry)
}
};
cpu.update_flags(rotated == 0, false, false, carry);
@@ -1149,14 +1152,14 @@ impl Instruction {
let byte = cpu.register(reg);
let shifted = byte << 1;
cpu.set_register(reg, shifted);
((8), (byte >> 7) & 0x01, shifted)
(8, (byte >> 7) & 0x01, shifted)
}
IndirectHL => {
let addr = cpu.register_pair(RegisterPair::HL);
let byte = Self::read_byte(&mut cpu.bus, addr);
let shifted = byte << 1;
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);
@@ -1172,14 +1175,14 @@ impl Instruction {
let byte = cpu.register(reg);
let shifted = ((byte >> 7) & 0x01) << 7 | byte >> 1;
cpu.set_register(reg, shifted);
((8), byte & 0x01, shifted)
(8, byte & 0x01, shifted)
}
IndirectHL => {
let addr = cpu.register_pair(RegisterPair::HL);
let byte = Self::read_byte(&mut cpu.bus, addr);
let shifted = ((byte >> 7) & 0x01) << 7 | byte >> 1;
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);
@@ -1194,14 +1197,14 @@ impl Instruction {
let reg = reg.cpu_register();
let swapped = Self::swap_bits(cpu.register(reg));
cpu.set_register(reg, swapped);
((8), swapped)
(8, swapped)
}
IndirectHL => {
let addr = cpu.register_pair(RegisterPair::HL);
let swapped = Self::swap_bits(Self::read_byte(&mut cpu.bus, addr));
Self::write_byte(&mut cpu.bus, addr, swapped);
((16), swapped)
(16, swapped)
}
};
cpu.update_flags(swapped == 0, false, false, false);
@@ -1217,14 +1220,14 @@ impl Instruction {
let byte = cpu.register(reg);
let shifted = byte >> 1;
cpu.set_register(reg, shifted);
((8), byte & 0x01, shifted)
(8, byte & 0x01, shifted)
}
IndirectHL => {
let addr = cpu.register_pair(RegisterPair::HL);
let byte = Self::read_byte(&mut cpu.bus, addr);
let shifted = byte >> 1;
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);
@@ -1239,12 +1242,12 @@ impl Instruction {
B | C | D | E | H | L | A => {
let reg = reg.cpu_register();
let byte = cpu.register(reg);
((8), ((byte >> bit) & 0x01) == 0x01)
(8, ((byte >> bit) & 0x01) == 0x01)
}
IndirectHL => {
let addr = cpu.register_pair(RegisterPair::HL);
let byte = Self::read_byte(&mut cpu.bus, addr);
((12), ((byte >> bit) & 0x01) == 0x01)
(12, ((byte >> bit) & 0x01) == 0x01)
}
};
flags.set_z(!is_set);
@@ -1893,7 +1896,7 @@ mod table {
}
impl Group1RegisterPair {
pub fn as_register_pair(&self) -> RegisterPair {
pub(crate) fn as_register_pair(&self) -> RegisterPair {
use Group1RegisterPair::*;
match self {
@@ -1927,7 +1930,7 @@ mod table {
}
impl Group2RegisterPair {
pub fn as_register_pair(&self) -> RegisterPair {
pub(crate) fn as_register_pair(&self) -> RegisterPair {
use Group2RegisterPair::*;
match self {
@@ -1961,7 +1964,7 @@ mod table {
}
impl Group3RegisterPair {
pub fn as_register_pair(&self) -> RegisterPair {
pub(crate) fn as_register_pair(&self) -> RegisterPair {
use Group3RegisterPair::*;
match self {
@@ -2003,7 +2006,7 @@ mod table {
}
impl Register {
pub fn cpu_register(&self) -> CpuRegister {
pub(crate) fn cpu_register(&self) -> CpuRegister {
use Register::*;
match self {

View File

@@ -1,11 +1,20 @@
use bitfield::bitfield;
#[derive(Debug, Default)]
#[derive(Debug)]
pub(crate) struct Interrupt {
pub(crate) flag: InterruptFlag,
pub(crate) enable: InterruptEnable,
}
impl Default for Interrupt {
fn default() -> Self {
Self {
flag: InterruptFlag(0),
enable: InterruptEnable(0),
}
}
}
bitfield! {
pub struct InterruptEnable(u8);
impl Debug;
@@ -23,12 +32,6 @@ impl Clone for InterruptEnable {
}
}
impl Default for InterruptEnable {
fn default() -> Self {
Self(0)
}
}
impl From<u8> for InterruptEnable {
fn from(byte: u8) -> Self {
Self(byte)
@@ -58,12 +61,6 @@ impl Clone for InterruptFlag {
}
}
impl Default for InterruptFlag {
fn default() -> Self {
Self(0)
}
}
impl From<u8> for InterruptFlag {
fn from(byte: u8) -> Self {
Self(byte)

View File

@@ -1,5 +1,5 @@
use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType};
use winit_input_helper::WinitInputHelper;
use winit::event::{ElementState, KeyboardInput, VirtualKeyCode};
#[derive(Debug)]
pub struct Joypad {
@@ -110,72 +110,39 @@ impl ButtonEvent {
}
}
pub fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) {
use winit::event::VirtualKeyCode;
// TODO: What do I have to do to get a match statement here?
#[inline]
pub(crate) fn handle_keyboard_input(pad: &mut Joypad, key: KeyboardInput) {
let state = &mut pad.ext;
let irq = &mut pad.interrupt;
if input.key_pressed(VirtualKeyCode::Down) {
state.dpad_down.update(true, irq);
}
if input.key_released(VirtualKeyCode::Down) {
state.dpad_down.update(false, irq);
}
if input.key_pressed(VirtualKeyCode::Up) {
state.dpad_up.update(true, irq);
}
if input.key_released(VirtualKeyCode::Up) {
state.dpad_up.update(false, irq);
}
if input.key_pressed(VirtualKeyCode::Left) {
state.dpad_left.update(true, irq);
}
if input.key_released(VirtualKeyCode::Left) {
state.dpad_left.update(false, irq);
}
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);
match key.state {
ElementState::Pressed => match key.virtual_keycode {
Some(VirtualKeyCode::Down) => state.dpad_down.update(true, irq),
Some(VirtualKeyCode::Up) => state.dpad_up.update(true, irq),
Some(VirtualKeyCode::Left) => state.dpad_left.update(true, 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),
Some(VirtualKeyCode::Z) => state.south.update(true, irq),
Some(VirtualKeyCode::X) => state.east.update(true, irq),
None | Some(_) => {}
},
ElementState::Released => match key.virtual_keycode {
Some(VirtualKeyCode::Down) => state.dpad_down.update(false, irq),
Some(VirtualKeyCode::Up) => state.dpad_up.update(false, irq),
Some(VirtualKeyCode::Left) => state.dpad_left.update(false, irq),
Some(VirtualKeyCode::Right) => state.dpad_right.update(false, irq),
Some(VirtualKeyCode::Return) => state.start.update(false, irq),
Some(VirtualKeyCode::RShift) => state.select.update(false, irq),
Some(VirtualKeyCode::Z) => state.south.update(false, irq),
Some(VirtualKeyCode::X) => state.east.update(false, irq),
None | Some(_) => {}
},
}
}
pub fn handle_gamepad_input(pad: &mut Joypad, event: GamepadEvent) {
#[inline]
pub(crate) fn handle_gamepad_input(pad: &mut Joypad, event: GamepadEvent) {
use Button::*;
use GamepadEventType::*;

View File

@@ -1,4 +1,4 @@
pub use apu::gen::AudioSPSC;
pub use apu::gen::init as spsc_init;
pub type Cycle = u64;
pub const GB_WIDTH: usize = 160;
@@ -9,6 +9,7 @@ mod bus;
mod cartridge;
mod cpu;
pub mod emu;
pub mod gui;
mod high_ram;
mod instruction;
mod interrupt;

View File

@@ -1,19 +1,18 @@
use anyhow::{anyhow, Result};
use std::time::Instant;
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
use gb::{AudioSPSC, Cycle, GB_HEIGHT, GB_WIDTH};
use egui_wgpu_backend::RenderPass;
use gb::{emu, gui};
use gilrs::Gilrs;
use pixels::{PixelsBuilder, SurfaceTexture};
use gui::GuiState;
use rodio::{OutputStream, Sink};
use winit::dpi::{LogicalSize, PhysicalSize};
use winit::event::{Event, VirtualKeyCode};
use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::{Window, WindowBuilder};
use winit_input_helper::WinitInputHelper;
use tracing_subscriber::EnvFilter;
use winit::event::{Event, WindowEvent};
use winit::event_loop::EventLoop;
const WINDOW_SCALE: usize = 3;
const AUDIO_ENABLED: bool = false;
const AUDIO_ENABLED: bool = true;
fn main() -> Result<()> {
fn main() {
let app = App::new(crate_name!())
.version(crate_version!())
.author(crate_authors!())
@@ -24,7 +23,6 @@ fn main() -> Result<()> {
Arg::with_name("rom")
.value_name("ROM_FILE")
.takes_value(true)
.required(true)
.index(1)
.help("Path to the Game ROM"),
)
@@ -38,93 +36,162 @@ fn main() -> Result<()> {
)
.get_matches();
let rom_path = m
.value_of("rom")
.expect("Required value 'rom' was provided");
// Set up subscriber
if std::env::var("RUST_LOG").is_err() {
std::env::set_var("RUST_LOG", "gb=info");
}
let mut game_boy =
gb::emu::init(m.value_of("boot"), rom_path).expect("Initialize DMG-01 Emulator");
let rom_title = gb::emu::rom_title(&game_boy);
tracing_subscriber::fmt::fmt()
.with_env_filter(EnvFilter::from_default_env())
.init();
let mut gamepad = Gilrs::new().expect("Initialize Controller Support");
// --Here lies a lot of winit + wgpu Boilerplate--
let event_loop: EventLoop<Event<()>> = EventLoop::with_user_event();
let window = gui::build_window(&event_loop).expect("build window");
// Initialize GUI
let event_loop = EventLoop::new();
let mut input = WinitInputHelper::new();
let window = create_window(&event_loop, rom_title)?;
let (instance, surface) = gui::create_surface(&window);
let adapter = gui::request_adapter(&instance, &surface).expect("request adaptor");
let (device, queue) = gui::request_device(&adapter).expect("request device");
let format = surface
.get_preferred_format(&adapter)
.expect("get surface format");
let mut pixels = {
let size = window.inner_size();
let surface_texture = SurfaceTexture::new(size.width, size.height, &window);
let mut config = gui::surface_config(&window, format);
surface.configure(&device, &config);
let mut platform = gui::platform_desc(&window);
let mut render_pass = RenderPass::new(&device, format, 1);
PixelsBuilder::new(GB_WIDTH as u32, GB_HEIGHT as u32, surface_texture)
.enable_vsync(false)
.build()?
// We interrupt your boiler plate to initialize the emulator so that
// we can copy it's empty pixel buffer to the GPU
let mut cpu = match m.value_of("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()
}
};
// Set up the wgpu (and then EGUI) texture we'll be working with.
let texture_size = gui::texture_size();
let texture = gui::create_texture(&device, texture_size);
gui::write_to_texture(&queue, &texture, emu::pixel_buf(&cpu), texture_size);
let texture_id = gui::expose_texture_to_egui(&mut render_pass, &device, &texture);
// Load ROM if filepath was provided
if let Some(path) = m.value_of("rom") {
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");
// Initialize Audio
let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio");
if AUDIO_ENABLED {
let spsc: AudioSPSC<f32> = Default::default();
let (prod, cons) = spsc.init();
let sink = Sink::try_new(&stream_handle)?;
sink.append(cons);
sink.set_volume(0.1); // TODO: Is this the right way to go about this?
game_boy.apu_mut().attach_producer(prod);
let (prod, cons) = gb::spsc_init();
let sink = {
let s = Sink::try_new(&stream_handle).expect("create sink from audio stream handle");
s.append(cons);
s.set_volume(0.1);
s
};
emu::set_audio_prod(&mut cpu, prod);
tracing::info!("Spawn Audio Thread");
std::thread::spawn(move || {
sink.sleep_until_end();
});
}
let mut cycle_count: Cycle = Default::default();
// Set up state for the Immediate-mode GUI
let mut app = GuiState::new(rom_title);
let mut last_key = gui::unused_key();
// used for egui animations
let start_time = Instant::now();
event_loop.run(move |event, _, control_flow| {
if let Event::RedrawRequested(_) = event {
if pixels
.render()
.map_err(|e| anyhow!("pixels.render() failed: {}", e))
.is_err()
{
*control_flow = ControlFlow::Exit;
return;
}
platform.handle_event(&event);
match event {
Event::MainEventsCleared => {
if app.quit {
emu::save_and_exit(&cpu, control_flow);
}
if input.update(&event) {
if input.key_pressed(VirtualKeyCode::Escape) || input.quit() {
*control_flow = ControlFlow::Exit;
return;
}
emu::run_frame(&mut cpu, &mut gamepad, last_key);
if let Some(size) = input.window_resized() {
pixels.resize_surface(size.width, size.height);
}
cycle_count += gb::emu::run_frame(&mut game_boy, &mut gamepad, &input);
if cycle_count >= gb::emu::CYCLES_IN_FRAME {
cycle_count %= gb::emu::CYCLES_IN_FRAME;
gb::emu::draw(game_boy.ppu(), pixels.get_frame());
window.request_redraw();
}
Event::RedrawRequested(..) => {
platform.update_time(start_time.elapsed().as_secs_f64());
let data = emu::pixel_buf(&cpu);
gui::write_to_texture(&queue, &texture, data, texture_size);
let output_frame = match surface.get_current_texture() {
Ok(frame) => frame,
Err(e) => {
eprintln!("Dropped frame with error: {}", e);
return;
}
};
let output_view = gui::create_view(&output_frame);
// Begin to draw Egui components
platform.begin_frame();
gui::draw_egui(&mut app, &platform.context(), texture_id);
// End the UI frame. We could now handle the output and draw the UI with the backend.
let (_, paint_commands) = platform.end_frame(Some(&window));
let paint_jobs = platform.context().tessellate(paint_commands);
let mut encoder = gui::create_command_encoder(&device);
let screen_descriptor = gui::create_screen_descriptor(&window, &config);
// Upload all resources for the GPU.
render_pass.update_texture(&device, &queue, &platform.context().texture());
render_pass.update_user_textures(&device, &queue);
render_pass.update_buffers(&device, &queue, &paint_jobs, &screen_descriptor);
// Record all render passes.
gui::execute_render_pass(
&mut render_pass,
&mut encoder,
&output_view,
paint_jobs,
&screen_descriptor,
)
.expect("record render passes");
// Submit the commands.
queue.submit(std::iter::once(encoder.finish()));
// Redraw egui
output_frame.present();
}
Event::WindowEvent { event, .. } => match event {
WindowEvent::Resized(size) => {
config.width = size.width;
config.height = size.height;
surface.configure(&device, &config);
}
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)?)
}

View File

@@ -4,7 +4,6 @@ use crate::GB_HEIGHT;
use crate::GB_WIDTH;
use dma::DirectMemoryAccess;
use std::collections::VecDeque;
use std::convert::TryInto;
pub(crate) use types::PpuMode;
use types::{
BackgroundPalette, GrayShade, LCDControl, LCDStatus, ObjectFlags, ObjectPalette,
@@ -43,20 +42,20 @@ pub struct Ppu {
pub(crate) monochrome: Monochrome,
pub(crate) pos: ScreenPosition,
vram: Box<[u8; VRAM_SIZE]>,
pub(crate) oam: ObjectAttributeTable,
pub(crate) oam: ObjectAttrTable,
pub(crate) dma: DirectMemoryAccess,
scan_state: OamScanState,
scan_dot: Cycle,
fetch: PixelFetcher,
fifo: PixelFifo,
obj_buffer: ObjectBuffer,
frame_buf: Box<[u8; GB_WIDTH * GB_HEIGHT * 4]>,
window_stat: WindowStatus,
pub(crate) frame_buf: Box<[u8; GB_WIDTH * GB_HEIGHT * 4]>,
win_stat: WindowStatus,
scanline_start: bool,
to_discard: u8,
x_pos: u8,
cycle: Cycle,
dot: Cycle,
}
impl BusIo for Ppu {
@@ -71,7 +70,7 @@ impl BusIo for Ppu {
impl Ppu {
pub(crate) fn tick(&mut self) {
self.cycle += 1;
self.dot += 1;
if !self.ctrl.lcd_enabled() {
return;
@@ -79,7 +78,26 @@ impl Ppu {
match self.stat.mode() {
PpuMode::OamScan => {
if self.cycle >= 80 {
// Cycles 1 -> 80
if self.dot >= 80 {
self.x_pos = 0;
self.scanline_start = true;
self.fetch.back.tile_high_reset = true;
self.to_discard = 0;
self.fifo.back.clear();
self.fifo.obj.clear();
// Sort Sprites
self.obj_buffer.inner.sort_by(|left, right| {
left.zip(*right)
.map(|(left, right)| right.x.cmp(&left.x))
.unwrap_or(std::cmp::Ordering::Greater)
});
// if self.obj_buffer.len != 0 {
// dbg!(&self.obj_buffer);
// }
self.stat.set_mode(PpuMode::Drawing);
}
@@ -88,7 +106,7 @@ impl Ppu {
PpuMode::Drawing => {
if self.ctrl.lcd_enabled() {
// Only Draw when the LCD Is Enabled
self.draw(self.cycle);
self.draw();
} else {
self.reset();
}
@@ -104,29 +122,22 @@ impl Ppu {
// Increment Window line counter if scanline had any window pixels on it
// only increment once per scanline though
if self.window_stat.should_draw() {
self.fetch.back.window_line.increment();
if self.win_stat.enabled {
self.fetch.back.wl_count += 1;
}
self.x_pos = 0;
self.scanline_start = true;
self.to_discard = 0;
self.fetch.hblank_reset();
self.window_stat.hblank_reset();
self.win_stat.enabled = false;
self.obj_buffer.clear();
self.fifo.back.clear();
self.fifo.obj.clear();
self.stat.set_mode(PpuMode::HBlank);
}
}
PpuMode::HBlank => {
// This mode will always end at 456 cycles
if self.cycle >= 456 {
self.cycle %= 456;
if self.dot >= 456 {
self.dot %= 456;
self.pos.line_y += 1;
// Update LY==LYC bit
@@ -143,9 +154,10 @@ impl Ppu {
self.int.set_vblank(true);
// Reset Window Line Counter in Fetcher
self.fetch.vblank_reset();
self.fetch.back.wl_count = 0;
// Reset WY=LY coincidence flag
self.window_stat.vblank_reset();
self.win_stat.coincidence = false;
if self.stat.vblank_int() {
// Enable Vblank LCDStat Interrupt
@@ -159,7 +171,7 @@ impl Ppu {
self.int.set_lcd_stat(true);
}
self.scan_state.reset();
self.scan_dot = Default::default();
PpuMode::OamScan
};
@@ -167,8 +179,8 @@ impl Ppu {
}
}
PpuMode::VBlank => {
if self.cycle > 456 {
self.cycle %= 456;
if self.dot >= 456 {
self.dot %= 456;
self.pos.line_y += 1;
// Update LY==LYC bit
@@ -188,8 +200,7 @@ impl Ppu {
self.int.set_lcd_stat(true);
}
self.scan_state.reset();
self.scan_dot = Default::default();
self.stat.set_mode(PpuMode::OamScan);
}
}
@@ -198,89 +209,81 @@ impl Ppu {
}
fn scan_oam(&mut self) {
match self.scan_state.mode() {
OamScanMode::Scan if !self.dma.is_active() => {
if !self.window_stat.coincidence() && self.scan_state.count() == 0 {
// Determine whether we should draw the window next frame
self.window_stat
.set_coincidence(self.pos.line_y == self.pos.window_y);
if self.scan_dot % 2 == 0 {
if self.dma.is_active() {
return;
}
let sprite_height = self.ctrl.obj_size().as_u8();
let index = self.scan_state.count();
if !self.win_stat.coincidence && self.scan_dot == 0 {
self.win_stat.coincidence = self.pos.line_y == self.pos.window_y;
}
let attr = self.oam.attribute(index as usize);
let obj_height = self.ctrl.obj_size().size();
let attr = self.oam.attribute(self.scan_dot as usize / 2);
let line_y = self.pos.line_y + 16;
if attr.x > 0
&& line_y >= attr.y
&& line_y < (attr.y + sprite_height)
&& line_y < (attr.y + obj_height)
&& !self.obj_buffer.is_full()
{
self.obj_buffer.add(attr);
}
self.scan_state.increase();
}
_ => {}
self.scan_dot += 1;
}
self.scan_state.next();
}
fn draw(&mut self, _cycle: Cycle) {
fn draw(&mut self) {
use FetcherState::*;
let mut iter = self.obj_buffer.iter_mut();
let default = &mut None;
let mut obj_attr = &mut None;
let obj_attr = loop {
match iter.next() {
Some(attr_opt) => {
if let Some(attr) = attr_opt {
for maybe_attr in &mut self.obj_buffer.inner {
match maybe_attr {
Some(attr) if self.ctrl.obj_enabled() => {
if attr.x <= (self.x_pos + 8) {
self.fetch.back.reset();
self.fetch.back.pause();
self.fetch.back.enabled = false;
self.fifo.pause();
break attr_opt;
obj_attr = maybe_attr;
break;
}
}
_ => break,
}
None => break default,
}
};
if let Some(attr) = obj_attr {
match self.fetch.obj.state {
TileNumber => {
TileNumberA => self.fetch.obj.state = TileNumberB,
TileNumberB => {
self.fetch.obj.tile.with_id(attr.tile_index);
self.fetch.obj.next(SleepOne);
self.fetch.obj.state = TileLowA;
}
SleepOne => self.fetch.obj.next(TileLow),
TileLow => {
TileLowA => self.fetch.obj.state = TileLowB,
TileLowB => {
let obj_size = self.ctrl.obj_size();
let addr = PixelFetcher::get_obj_addr(&attr, &self.pos, obj_size);
let addr = PixelFetcher::obj_addr(attr, &self.pos, obj_size);
let byte = self.read_byte(addr);
self.fetch.obj.tile.with_low_byte(byte);
self.fetch.obj.tile.with_low(byte);
self.fetch.obj.next(SleepTwo);
self.fetch.obj.state = TileHighA;
}
SleepTwo => self.fetch.obj.next(TileHigh),
TileHigh => {
TileHighA => self.fetch.obj.state = TileHighB,
TileHighB => {
let obj_size = self.ctrl.obj_size();
let addr = PixelFetcher::get_obj_addr(&attr, &self.pos, obj_size);
let addr = PixelFetcher::obj_addr(attr, &self.pos, obj_size);
let byte = self.read_byte(addr + 1);
self.fetch.obj.tile.with_high_byte(byte);
self.fetch.obj.tile.with_high(byte);
self.fetch.obj.next(SleepThree);
self.fetch.obj.state = ToFifoA;
}
SleepThree => self.fetch.obj.next(ToFifoOne),
ToFifoOne => {
ToFifoA => {
// Load into Fifo
let (high, low) = self
.fetch
@@ -312,73 +315,65 @@ impl Ppu {
self.fifo.obj.push_back(fifo_info);
}
self.fetch.back.resume();
self.fetch.back.enabled = true;
self.fifo.resume();
let _ = std::mem::take(obj_attr);
self.fetch.obj.next(ToFifoTwo);
self.fetch.obj.state = ToFifoB;
}
ToFifoTwo => self.fetch.obj.reset(),
ToFifoB => self.fetch.obj.reset(),
}
}
if self.ctrl.window_enabled()
&& !self.window_stat.should_draw()
&& self.window_stat.coincidence()
&& self.x_pos as i16 >= self.pos.window_x as i16 - 7
{
self.window_stat.set_should_draw(true);
self.fetch.back.reset();
self.fetch.x_pos = 0;
self.fifo.back.clear();
}
if self.fetch.back.is_enabled() {
if self.fetch.back.enabled {
match self.fetch.back.state {
TileNumber => {
let x_pos = self.fetch.x_pos;
TileNumberA => self.fetch.back.state = TileNumberB,
TileNumberB => {
// Are we rendering the Window currently?
self.fetch.back.draw_window = self.win_stat.enabled;
let addr =
self.fetch
.back
.should_render_window(self.window_stat.should_draw());
let addr = self.fetch.bg_tile_num_addr(&self.ctrl, &self.pos, x_pos);
.tile_id_addr(&self.ctrl, &self.pos, self.fetch.x_pos);
let id = self.read_byte(addr);
self.fetch.back.tile.with_id(id);
// Move on to the Next state in 2 T-cycles
self.fetch.back.next(SleepOne);
self.fetch.back.state = TileLowA;
}
SleepOne => self.fetch.back.next(TileLow),
TileLow => {
let addr = self.fetch.bg_byte_addr(&self.ctrl, &self.pos);
TileLowA => self.fetch.back.state = TileLowB,
TileLowB => {
let id = self.fetch.back.tile.id.expect("Tile ID present");
let low = self.read_byte(addr);
self.fetch.back.tile.with_low_byte(low);
let addr = self.fetch.back.tile_addr(&self.ctrl, &self.pos, id);
let byte = self.read_byte(addr);
self.fetch.back.tile.with_low(byte);
self.fetch.back.next(SleepTwo);
self.fetch.back.state = TileHighA;
}
SleepTwo => self.fetch.back.next(TileHigh),
TileHigh => {
let addr = self.fetch.bg_byte_addr(&self.ctrl, &self.pos);
TileHighA => self.fetch.back.state = TileHighB,
TileHighB => {
let id = self.fetch.back.tile.id.expect("Tile ID present");
let high = self.read_byte(addr + 1);
self.fetch.back.tile.with_high_byte(high);
let addr = self.fetch.back.tile_addr(&self.ctrl, &self.pos, id);
let byte = self.read_byte(addr + 1);
self.fetch.back.tile.with_high(byte);
self.fetch.back.next(SleepThree);
if self.fetch.back.tile_high_reset {
self.fetch.back.reset();
self.fetch.back.tile_high_reset = false;
} else {
self.fetch.back.state = ToFifoA;
}
SleepThree => self.fetch.back.next(ToFifoOne),
ToFifoOne => {
self.fetch.back.next(ToFifoTwo);
}
ToFifoTwo => {
if let Ok(()) = self.fetch.send_to_fifo(&mut self.fifo) {
ToFifoA => {
if self.fetch.send_to_fifo(&mut self.fifo).is_ok() {
self.fetch.x_pos += 1;
self.fetch.back.next(TileNumber);
self.fetch.back.tile = Default::default();
self.fetch.back.state = ToFifoB;
}
}
ToFifoB => self.fetch.back.reset(),
}
}
@@ -388,7 +383,7 @@ impl Ppu {
self.scanline_start = false;
}
if self.to_discard > 0 && !self.fifo.back.is_empty() {
if !self.win_stat.enabled && self.to_discard > 0 && !self.fifo.back.is_empty() {
let _ = self.fifo.back.pop_front();
self.to_discard -= 1;
@@ -406,6 +401,17 @@ impl Ppu {
self.x_pos += 1;
}
if self.ctrl.window_enabled()
&& !self.win_stat.enabled
&& self.win_stat.coincidence
&& self.x_pos as i16 >= self.pos.window_x as i16 - 7
{
self.win_stat.enabled = true;
self.fetch.back.reset();
self.fetch.x_pos = 0;
self.fifo.back.clear();
}
}
}
@@ -413,53 +419,44 @@ impl Ppu {
self.pos.line_y = 0;
self.stat.set_mode(PpuMode::HBlank);
// TODO: Is this an unnecessary performance hit?
let mut blank = WHITE.repeat(self.frame_buf.len() / 4);
self.frame_buf.swap_with_slice(&mut blank);
}
pub fn copy_to_gui(&self, frame: &mut [u8]) {
frame.copy_from_slice(self.frame_buf.as_ref());
}
fn clock_fifo(&mut self) -> Option<GrayShade> {
use ObjectPaletteKind::*;
use RenderPriority::*;
let obj_palette_0 = &self.monochrome.obj_palette_0;
let obj_palette_1 = &self.monochrome.obj_palette_1;
match self.fifo.back.pop_front() {
Some(bg_pixel) => match self.fifo.obj.pop_front() {
Some(obj_pixel) if self.ctrl.obj_enabled() => match obj_pixel.priority {
Object | BackgroundAndWindow if obj_pixel.shade_id == 0 => {
Some(self.bg_pixel(bg_pixel.shade_id))
}
BackgroundAndWindow if bg_pixel.shade_id != 0 => {
Some(self.bg_pixel(bg_pixel.shade_id))
}
Object | BackgroundAndWindow => {
let maybe_sprite = match obj_pixel.palette_kind {
Zero => obj_palette_0.shade(obj_pixel.shade_id),
One => obj_palette_1.shade(obj_pixel.shade_id),
};
let sprite = maybe_sprite
.expect("Sprite w/ a colour id of 0 has already been handled");
Some(sprite)
}
self.fifo
.back
.pop_front()
.map(|bg| match self.fifo.obj.pop_front() {
Some(obj) => match obj.priority {
_ if obj.shade_id == 0 => self.bg_pixel(bg),
BackgroundAndWindow if bg.shade_id != 0 => self.bg_pixel(bg),
_ => self.obj_pixel(obj),
},
_ => Some(self.bg_pixel(bg_pixel.shade_id)),
},
None => None,
None => self.bg_pixel(bg),
})
}
fn obj_pixel(&self, obj: ObjPixelProperty) -> GrayShade {
use ObjectPaletteKind::*;
assert_ne!(obj.shade_id, 0);
let p0 = &self.monochrome.obj_palette_0;
let p1 = &self.monochrome.obj_palette_1;
match obj.palette_kind {
Zero => p0.shade(obj.shade_id).expect("Object shade id is non-zero"),
One => p1.shade(obj.shade_id).expect("Object shade id is non-zero"),
}
}
fn bg_pixel(&self, shade_id: u8) -> GrayShade {
fn bg_pixel(&self, bg: BgPixelProperty) -> GrayShade {
let bg_palette = &self.monochrome.bg_palette;
if self.ctrl.bg_win_enabled() {
bg_palette.shade(shade_id)
bg_palette.shade(bg.shade_id)
} else {
bg_palette.shade(0)
}
@@ -470,19 +467,19 @@ impl Default for Ppu {
fn default() -> Self {
Self {
vram: Box::new([0u8; VRAM_SIZE]),
cycle: Default::default(),
dot: Default::default(),
frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]),
int: Default::default(),
ctrl: Default::default(),
ctrl: LCDControl(0),
monochrome: Default::default(),
pos: Default::default(),
stat: Default::default(),
stat: LCDStatus(0x80), // bit 7 is always 1
oam: Default::default(),
scan_state: Default::default(),
scan_dot: Default::default(),
fetch: Default::default(),
fifo: Default::default(),
obj_buffer: Default::default(),
window_stat: Default::default(),
win_stat: Default::default(),
dma: Default::default(),
x_pos: 0,
scanline_start: true,
@@ -531,7 +528,7 @@ pub(crate) struct ScreenPosition {
pub(crate) window_x: u8,
}
#[derive(Debug, Default)]
#[derive(Debug)]
pub(crate) struct Monochrome {
/// 0xFF47 | BGP - Background Palette Data
pub(crate) bg_palette: BackgroundPalette,
@@ -541,12 +538,22 @@ pub(crate) struct Monochrome {
pub(crate) obj_palette_1: ObjectPalette,
}
impl Default for Monochrome {
fn default() -> Self {
Self {
bg_palette: BackgroundPalette(0),
obj_palette_0: ObjectPalette(0),
obj_palette_1: ObjectPalette(0),
}
}
}
#[derive(Debug)]
pub(crate) struct ObjectAttributeTable {
pub(crate) struct ObjectAttrTable {
buf: Box<[u8; OAM_SIZE]>,
}
impl BusIo for ObjectAttributeTable {
impl BusIo for ObjectAttrTable {
fn read_byte(&self, addr: u16) -> u8 {
let index = (addr - 0xFE00) as usize;
self.buf[index]
@@ -558,8 +565,8 @@ impl BusIo for ObjectAttributeTable {
}
}
impl ObjectAttributeTable {
fn attribute(&self, index: usize) -> ObjectAttribute {
impl ObjectAttrTable {
fn attribute(&self, index: usize) -> ObjectAttr {
let start = index * 4;
let slice: &[u8; 4] = self.buf[start..(start + 4)]
@@ -570,7 +577,7 @@ impl ObjectAttributeTable {
}
}
impl Default for ObjectAttributeTable {
impl Default for ObjectAttrTable {
fn default() -> Self {
Self {
buf: Box::new([0; OAM_SIZE]),
@@ -578,15 +585,15 @@ impl Default for ObjectAttributeTable {
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
struct ObjectAttribute {
#[derive(Debug, Clone, Copy)]
struct ObjectAttr {
y: u8,
x: u8,
tile_index: u8,
flags: ObjectFlags,
}
impl From<[u8; 4]> for ObjectAttribute {
impl From<[u8; 4]> for ObjectAttr {
fn from(bytes: [u8; 4]) -> Self {
Self {
y: bytes[0],
@@ -597,7 +604,7 @@ impl From<[u8; 4]> for ObjectAttribute {
}
}
impl<'a> From<&'a [u8; 4]> for ObjectAttribute {
impl<'a> From<&'a [u8; 4]> for ObjectAttr {
fn from(bytes: &'a [u8; 4]) -> Self {
Self {
y: bytes[0],
@@ -610,7 +617,7 @@ impl<'a> From<&'a [u8; 4]> for ObjectAttribute {
#[derive(Debug)]
struct ObjectBuffer {
inner: [Option<ObjectAttribute>; OBJECT_LIMIT],
inner: [Option<ObjectAttr>; OBJECT_LIMIT],
len: usize,
}
@@ -624,14 +631,10 @@ impl ObjectBuffer {
self.len = 0;
}
fn add(&mut self, attr: ObjectAttribute) {
fn add(&mut self, attr: ObjectAttr) {
self.inner[self.len] = Some(attr);
self.len += 1;
}
fn iter_mut(&mut self) -> std::slice::IterMut<'_, Option<ObjectAttribute>> {
self.inner.iter_mut()
}
}
impl Default for ObjectBuffer {
@@ -657,61 +660,6 @@ impl PixelFetcher {
self.x_pos = 0;
}
fn vblank_reset(&mut self) {
self.back.vblank_reset();
}
fn bg_tile_num_addr(&self, control: &LCDControl, pos: &ScreenPosition, x_pos: u8) -> u16 {
let line_y = pos.line_y;
let scroll_y = pos.scroll_y;
let scroll_x = pos.scroll_x;
let is_window = self.back.is_window_tile();
// Determine which tile map is being used
let tile_map = if is_window {
control.win_tile_map_addr()
} else {
control.bg_tile_map_addr()
};
let tile_map_addr = tile_map.into_address();
// Both Offsets are used to offset the tile map address we found above
// Offsets are ANDed wih 0x3FF so that we stay in bounds of tile map memory
let scx_offset = if is_window { 0 } else { scroll_x / 8 };
let y_offset = if is_window {
self.back.window_line.count() as u16 / 8
} else {
((line_y as u16 + scroll_y as u16) & 0xFF) / 8
};
let x_offset = (scx_offset + x_pos) & 0x1F;
let offset = (32 * y_offset) + (x_offset as u16);
tile_map_addr + (offset & 0x3FF)
}
fn bg_byte_addr(&mut self, control: &LCDControl, pos: &ScreenPosition) -> u16 {
let line_y = pos.line_y;
let scroll_y = pos.scroll_y;
let is_window = self.back.is_window_tile();
let id = self.back.tile.id.expect("Tile Number is present");
let tile_data_addr = match control.tile_data_addr() {
TileDataAddress::X8800 => 0x9000u16.wrapping_add(((id as i8) as i16 * 16) as u16),
TileDataAddress::X8000 => 0x8000 + (id as u16 * 16),
};
let offset = if is_window {
self.back.window_line.count() as u16 % 8
} else {
(line_y as u16 + scroll_y as u16) % 8
};
tile_data_addr + (offset * 2)
}
fn send_to_fifo(&self, fifo: &mut PixelFifo) -> Result<(), ()> {
if !fifo.back.is_empty() {
return Err(());
@@ -735,7 +683,7 @@ impl PixelFetcher {
Ok(())
}
fn get_obj_addr(attr: &ObjectAttribute, pos: &ScreenPosition, size: ObjectSize) -> u16 {
fn obj_addr(attr: &ObjectAttr, pos: &ScreenPosition, size: ObjectSize) -> u16 {
let line_y = pos.line_y;
// TODO: Why is the offset 14 and 30 respectively?
@@ -757,7 +705,6 @@ impl PixelFetcher {
}
trait Fetcher {
fn next(&mut self, state: FetcherState);
fn reset(&mut self);
fn hblank_reset(&mut self);
}
@@ -766,42 +713,63 @@ trait Fetcher {
struct BackgroundFetcher {
state: FetcherState,
tile: TileBuilder,
window_line: WindowLineCounter,
is_window_tile: bool,
wl_count: u8,
draw_window: bool,
enabled: bool,
tile_high_reset: bool,
}
impl BackgroundFetcher {
fn should_render_window(&mut self, value: bool) {
self.is_window_tile = value;
fn tile_id_addr(&self, control: &LCDControl, pos: &ScreenPosition, x_pos: u8) -> u16 {
let line_y = pos.line_y;
let scroll_y = pos.scroll_y;
let scroll_x = pos.scroll_x;
let is_window = self.draw_window;
// Determine which tile map is being used
let tile_map = if is_window {
control.win_tile_map_addr()
} else {
control.bg_tile_map_addr()
};
let tile_map_addr = tile_map.into_address();
// Both Offsets are used to offset the tile map address we found above
// Offsets are ANDed wih 0x3FF so that we stay in bounds of tile map memory
let scx_offset = if is_window { 0 } else { scroll_x / 8 };
let y_offset = if is_window {
self.wl_count as u16 / 8
} else {
((line_y as u16 + scroll_y as u16) & 0xFF) / 8
};
let x_offset = (scx_offset + x_pos) & 0x1F;
let offset = (32 * y_offset) + (x_offset as u16);
tile_map_addr + (offset & 0x3FF)
}
fn is_window_tile(&self) -> bool {
self.is_window_tile
}
fn tile_addr(&mut self, control: &LCDControl, pos: &ScreenPosition, id: u8) -> u16 {
let line_y = pos.line_y;
let scroll_y = pos.scroll_y;
fn pause(&mut self) {
self.enabled = false;
}
let tile_data_addr = match control.tile_data_addr() {
TileDataAddress::X8800 => 0x9000u16.wrapping_add((id as i8 as i16 * 16) as u16),
TileDataAddress::X8000 => 0x8000 + (id as u16 * 16),
};
fn resume(&mut self) {
self.enabled = true;
}
let offset = if self.draw_window {
self.wl_count as u16 % 8
} else {
(line_y as u16 + scroll_y as u16) % 8
};
fn is_enabled(&self) -> bool {
self.enabled
}
fn vblank_reset(&mut self) {
self.window_line.vblank_reset();
tile_data_addr + (offset * 2)
}
}
impl Fetcher for BackgroundFetcher {
fn next(&mut self, state: FetcherState) {
self.state = state
}
fn reset(&mut self) {
self.state = Default::default();
self.tile = Default::default();
@@ -810,7 +778,7 @@ impl Fetcher for BackgroundFetcher {
fn hblank_reset(&mut self) {
self.reset();
self.is_window_tile = false;
self.draw_window = false;
self.enabled = true;
}
@@ -821,69 +789,55 @@ impl Default for BackgroundFetcher {
Self {
state: Default::default(),
tile: Default::default(),
is_window_tile: Default::default(),
window_line: Default::default(),
draw_window: Default::default(),
wl_count: Default::default(),
enabled: true,
tile_high_reset: true,
}
}
}
#[derive(Debug, Default)]
#[derive(Debug)]
struct ObjectFetcher {
state: FetcherState,
tile: TileBuilder,
}
impl Fetcher for ObjectFetcher {
fn next(&mut self, state: FetcherState) {
self.state = state
impl Default for ObjectFetcher {
fn default() -> Self {
Self {
state: Default::default(),
tile: Default::default(),
}
}
}
impl Fetcher for ObjectFetcher {
fn reset(&mut self) {
self.state = Default::default();
self.tile = Default::default();
}
fn hblank_reset(&mut self) {
self.state = Default::default();
self.tile = Default::default();
}
}
#[derive(Debug, Default)]
struct WindowLineCounter {
count: u8,
}
impl WindowLineCounter {
fn increment(&mut self) {
self.count += 1;
}
fn vblank_reset(&mut self) {
self.count = 0;
}
fn count(&self) -> u8 {
self.count
self.reset()
}
}
#[derive(Debug, Clone, Copy)]
enum FetcherState {
TileNumber,
SleepOne,
TileLow,
SleepTwo,
TileHigh,
SleepThree,
ToFifoOne,
ToFifoTwo,
TileNumberA,
TileNumberB,
TileLowA,
TileLowB,
TileHighA,
TileHighB,
ToFifoA,
ToFifoB,
}
impl Default for FetcherState {
fn default() -> Self {
Self::TileNumber
Self::TileNumberA
}
}
@@ -892,7 +846,7 @@ struct BgPixelProperty {
shade_id: u8,
}
#[derive(Debug, Default)]
#[derive(Debug)]
struct ObjPixelProperty {
shade_id: u8,
palette_kind: ObjectPaletteKind,
@@ -944,11 +898,11 @@ impl TileBuilder {
self.id = Some(id);
}
fn with_low_byte(&mut self, data: u8) {
fn with_low(&mut self, data: u8) {
self.low = Some(data);
}
fn with_high_byte(&mut self, data: u8) {
fn with_high(&mut self, data: u8) {
self.high = Some(data);
}
@@ -957,84 +911,11 @@ impl TileBuilder {
}
}
#[derive(Debug, Default)]
struct OamScanState {
count: u8,
mode: OamScanMode,
}
impl OamScanState {
fn increase(&mut self) {
self.count += 1;
self.count %= 40;
}
fn reset(&mut self) {
self.count = Default::default();
self.mode = Default::default();
}
fn count(&self) -> u8 {
self.count
}
fn mode(&self) -> &OamScanMode {
&self.mode
}
fn next(&mut self) {
use OamScanMode::*;
self.mode = match self.mode {
Scan => Sleep,
Sleep => Scan,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum OamScanMode {
Scan,
Sleep,
}
impl Default for OamScanMode {
fn default() -> Self {
Self::Scan
}
}
#[derive(Debug, Default)]
struct WindowStatus {
/// This will be true if WY == LY at any point in the frame thus far
coincidence: bool,
/// This will be true if the conditions which tell the PPU to start
/// drawing from the window tile map is true
should_draw: bool,
}
impl WindowStatus {
fn should_draw(&self) -> bool {
self.should_draw
}
fn coincidence(&self) -> bool {
self.coincidence
}
fn set_should_draw(&mut self, value: bool) {
self.should_draw = value;
}
fn set_coincidence(&mut self, value: bool) {
self.coincidence = value;
}
fn hblank_reset(&mut self) {
self.should_draw = false;
}
fn vblank_reset(&mut self) {
self.coincidence = false;
}
enabled: bool,
}

View File

@@ -1,6 +1,6 @@
use crate::Cycle;
#[derive(Debug, Default)]
#[derive(Debug)]
pub(crate) struct DirectMemoryAccess {
pub(crate) state: DmaState,
cycle: Cycle,
@@ -8,6 +8,16 @@ pub(crate) struct DirectMemoryAccess {
pub(crate) start: DmaAddress,
}
impl Default for DirectMemoryAccess {
fn default() -> Self {
Self {
state: DmaState::Disabled,
cycle: Default::default(),
start: Default::default(),
}
}
}
impl DirectMemoryAccess {
pub(crate) fn tick(&mut self) -> Option<(u16, u16)> {
match self.state {
@@ -69,12 +79,6 @@ pub(crate) enum DmaState {
Transferring,
}
impl Default for DmaState {
fn default() -> Self {
Self::Disabled
}
}
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct DmaAddress(Option<u16>);

View File

@@ -29,12 +29,6 @@ impl Clone for LCDStatus {
}
}
impl Default for LCDStatus {
fn default() -> Self {
Self(0x80) // bit 7 is always 1
}
}
impl From<LCDStatus> for u8 {
fn from(status: LCDStatus) -> Self {
status.0
@@ -67,12 +61,6 @@ impl From<PpuMode> for u8 {
}
}
impl Default for PpuMode {
fn default() -> Self {
Self::HBlank
}
}
bitfield! {
pub struct LCDControl(u8);
impl Debug;
@@ -93,12 +81,6 @@ impl Clone for LCDControl {
}
}
impl Default for LCDControl {
fn default() -> Self {
Self(0)
}
}
impl From<u8> for LCDControl {
fn from(byte: u8) -> Self {
Self(byte)
@@ -142,12 +124,6 @@ impl From<TileMapAddress> for u8 {
}
}
impl Default for TileMapAddress {
fn default() -> Self {
Self::X9800
}
}
#[derive(Debug, Clone, Copy)]
pub enum TileDataAddress {
X8800 = 0,
@@ -170,12 +146,6 @@ impl From<TileDataAddress> for u8 {
}
}
impl Default for TileDataAddress {
fn default() -> Self {
Self::X8800
}
}
#[derive(Debug, Clone, Copy)]
pub enum ObjectSize {
Eight = 0,
@@ -183,7 +153,7 @@ pub enum ObjectSize {
}
impl ObjectSize {
pub(crate) fn as_u8(&self) -> u8 {
pub(crate) fn size(&self) -> u8 {
use ObjectSize::*;
match self {
@@ -209,12 +179,6 @@ impl From<ObjectSize> for u8 {
}
}
impl Default for ObjectSize {
fn default() -> Self {
Self::Eight
}
}
bitfield! {
pub struct BackgroundPalette(u8);
impl Debug;
@@ -243,12 +207,6 @@ impl Clone for BackgroundPalette {
}
}
impl Default for BackgroundPalette {
fn default() -> Self {
Self(0)
}
}
impl From<u8> for BackgroundPalette {
fn from(byte: u8) -> Self {
Self(byte)
@@ -288,12 +246,6 @@ impl Clone for ObjectPalette {
}
}
impl Default for ObjectPalette {
fn default() -> Self {
Self(0)
}
}
impl From<u8> for ObjectPalette {
fn from(byte: u8) -> Self {
Self(byte)
@@ -361,24 +313,12 @@ impl From<ObjectFlags> for u8 {
}
}
impl Default for ObjectFlags {
fn default() -> Self {
Self(0)
}
}
#[derive(Debug, Clone, Copy)]
pub enum ObjectPaletteKind {
Zero = 0,
One = 1,
}
impl Default for ObjectPaletteKind {
fn default() -> Self {
Self::Zero
}
}
impl From<u8> for ObjectPaletteKind {
fn from(byte: u8) -> Self {
match byte & 0b01 {
@@ -417,12 +357,6 @@ impl From<RenderPriority> for u8 {
}
}
impl Default for RenderPriority {
fn default() -> Self {
Self::Object
}
}
#[derive(Debug, Clone, Copy)]
pub enum GrayShade {
White = 0,
@@ -442,12 +376,6 @@ impl GrayShade {
}
}
impl Default for GrayShade {
fn default() -> Self {
Self::White
}
}
impl From<u8> for GrayShade {
fn from(byte: u8) -> Self {
match byte & 0b11 {

View File

@@ -1,79 +0,0 @@
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,
}

View File

@@ -1,6 +1,6 @@
use bitfield::bitfield;
#[derive(Debug, Default)]
#[derive(Debug)]
pub(crate) struct Serial {
/// 0xFF01 | SB - Serial Transfer Data
pub(crate) next: u8,
@@ -8,6 +8,15 @@ pub(crate) struct Serial {
pub(crate) ctrl: SerialControl,
}
impl Default for Serial {
fn default() -> Self {
Self {
next: Default::default(),
ctrl: SerialControl(0),
}
}
}
bitfield! {
pub struct SerialControl(u8);
impl Debug;
@@ -23,12 +32,6 @@ impl Clone for SerialControl {
}
}
impl Default for SerialControl {
fn default() -> Self {
Self(0)
}
}
impl From<u8> for SerialControl {
fn from(byte: u8) -> Self {
Self(byte)
@@ -47,12 +50,6 @@ enum ShiftClock {
Internal = 1,
}
impl Default for ShiftClock {
fn default() -> Self {
Self::External
}
}
impl From<u8> for ShiftClock {
fn from(byte: u8) -> Self {
match byte & 0b01 {
@@ -69,12 +66,6 @@ enum ClockSpeed {
Fast = 1,
}
impl Default for ClockSpeed {
fn default() -> Self {
Self::Normal
}
}
impl From<u8> for ClockSpeed {
fn from(byte: u8) -> Self {
match byte & 0b01 {

View File

@@ -21,17 +21,6 @@ impl Timer {
use State::*;
use TimerSpeed::*;
match self.state {
TIMAOverflow(_) | AbortedTIMAOverflow(_) => self.next(),
LoadTMA => {
self.counter = self.modulo;
self.interrupt = true;
self.next();
}
Normal => {}
}
self.divider = self.divider.wrapping_add(1);
// Get Bit Position
@@ -53,6 +42,17 @@ impl Timer {
}
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
@@ -70,7 +70,7 @@ impl Timer {
self.counter = byte;
self.state = AbortedTIMAOverflow(step);
}
LoadTMA => {}
LoadTIMA => { /* Ignored */ }
}
}
@@ -95,9 +95,8 @@ impl Timer {
use State::*;
self.state = match self.state {
Normal | LoadTMA => Normal,
AbortedTIMAOverflow(4) => Normal,
TIMAOverflow(4) => LoadTMA,
Normal | LoadTIMA | AbortedTIMAOverflow(3) => Normal,
TIMAOverflow(3) => LoadTIMA,
AbortedTIMAOverflow(step) => AbortedTIMAOverflow(step + 1),
TIMAOverflow(step) => TIMAOverflow(step + 1),
}
@@ -107,7 +106,7 @@ impl Timer {
impl Default for Timer {
fn default() -> Self {
Self {
ctrl: Default::default(),
ctrl: TimerControl(0),
counter: 0,
modulo: 0,
divider: 0,
@@ -158,12 +157,6 @@ impl Clone for TimerControl {
}
}
impl Default for TimerControl {
fn default() -> Self {
Self(0)
}
}
impl From<u8> for TimerControl {
fn from(byte: u8) -> Self {
Self(byte)
@@ -181,5 +174,5 @@ enum State {
TIMAOverflow(u8),
AbortedTIMAOverflow(u8),
Normal,
LoadTMA,
LoadTIMA,
}

View File

@@ -11,13 +11,13 @@ pub(crate) struct WorkRam {
}
impl BusIo for WorkRam {
fn write_byte(&mut self, addr: u16, byte: u8) {
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]
}
fn write_byte(&mut self, addr: u16, byte: u8) {
self.bank[addr as usize - WORK_RAM_START_ADDRESS] = byte;
}
}
impl Default for WorkRam {
@@ -42,11 +42,11 @@ impl Default for VariableWorkRam {
}
impl BusIo for VariableWorkRam {
fn write_byte(&mut self, addr: u16, byte: u8) {
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]
}
fn write_byte(&mut self, addr: u16, byte: u8) {
self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] = byte;
}
}