Compare commits

...

73 Commits

Author SHA1 Message Date
9baa15050e in progress refactor
Some checks reported errors
continuous-integration/drone/push Build was killed
Needs profiling
2021-09-17 23:29:55 -03:00
4516ca8477 chore: run cargo fix
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-12 04:56:39 -03:00
6087e3b20b chore: remove Cycle struct and begin scheduler design 2021-09-12 04:56:34 -03:00
10ac579c40 fix(main): Use LogicalSize and PhysicalSize properly 2021-09-11 22:56:40 -03:00
ee5504111b Merge branch 'main' of ssh://git.musuka.dev:2222/paoda/gb into main
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-11 20:48:05 -03:00
a628f64d28 chore: update dependencies 2021-09-11 20:47:45 -03:00
318a6e0386 fix(emu): remove GAMEPAD_ENABLED const flag
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-09 11:12:50 -03:00
db012c7f4b fix(main): remove code unrelated to audio sync
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-07 02:09:02 -03:00
e42c87aeb7 fix(apu): quiet gameboy APU 2021-09-07 01:52:02 -03:00
9113e95fa0 fix(apu): pass blargg apu sweep tests 2021-09-07 01:17:01 -03:00
9973dc8714 fix(cartridge): don't read from RAM that doesn't exist
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-06 23:45:04 -03:00
e128025208 fix(cartridge): improve accuracy of MBC1 with large file sizes 2021-09-06 23:37:55 -03:00
44ac0c8ebd feat(cartridge): implement MBC2
All checks were successful
continuous-integration/drone/push Build is passing
2021-09-06 18:13:34 -03:00
01064bab69 chore(cpu): comment out blargg-specific code
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-22 01:49:01 -05:00
634bc2d2c0 fix(apu): remove redundant code 2021-08-22 01:48:34 -05:00
d794a94b68 fix(timer): increase accuracy of timer
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-20 00:17:28 -05:00
b87e31d3f4 fix(cartridge): remove unnecessary dbg statement 2021-08-20 00:17:05 -05:00
3c2456611e fix(cartridge): trim whitespace from cartridge title
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-19 21:56:07 -05:00
b829f05a34 chore(cartridge): clean-up code
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-19 20:39:04 -05:00
afd2d16371 chore(cartridge): re-rename RamInfo and RomRinfo 2021-08-19 20:05:48 -05:00
1f8fa48168 chore: add reccomended vscode extensions
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-19 17:05:36 -05:00
c6fbb79189 chore(cartridge): reognanize code 2021-08-19 16:10:39 -05:00
8b78b2943e fix(cartridge): specify intentional overflow in MBC5
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-19 16:09:58 -05:00
0af95a1dd3 feat(cartridge): implement MBC5 and cleanup code
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-18 22:40:15 -05:00
9fa40f8584 fix(main): do not drop output stream 2021-08-18 22:39:55 -05:00
b10bc7b4fd chore(main): add flag that enables audio 2021-08-18 16:34:26 -05:00
4658a0d106 fix(apu): ch4 set_len should set timer to 64 - len
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-18 02:32:48 -05:00
f92b9d61ef chore(apu_gen): cosmetic changes to sample generation
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-18 01:43:38 -05:00
360a9a7b65 chore(apu): improve code organization
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-18 00:04:27 -05:00
5d64e539a7 fix(apu): resolve off-by-one error 2021-08-17 23:14:56 -05:00
22f96a10e7 fix(apu): increase accuracy of apu emulation
Reintroduce the Frame Sequencer and it's state enum (needed so that we
can reset the FS on NR52 enable)
2021-08-17 22:18:40 -05:00
8fea8eb1ff fix(apu): better emulate behaviour of apu channel DACs 2021-08-17 21:26:22 -05:00
9b2c91445a chore: enable audio by default
even if it sounds really bad right now
2021-08-17 21:25:55 -05:00
b9046bceba fix(apu): disable channel if DAC is disabled 2021-08-17 20:42:41 -05:00
aa22e93049 chore: clean up TODO messages
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-15 23:31:27 -05:00
6215eccb2f chore(cpu): merge halted and state properties 2021-08-15 23:26:01 -05:00
a77d0a0f62 fix(apu): clock frame sequencer at correct Hz
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-15 02:46:20 -05:00
c2f2e2194b chore(ppu): improve accuracy of pixel fifo 2021-08-14 23:47:16 -05:00
d68257bb29 fix(ppu): improve accuracy of SCX discard 2021-08-14 22:03:01 -05:00
e27d6dc25b chore(ppu): rename discriminants of fetcher state 2021-08-14 22:02:41 -05:00
1acb5de19d fix(main): GB frametime should be 59.73 Hz 2021-08-14 17:59:59 -05:00
1b78b248a3 chore: minor edits to documentation 2021-08-14 17:51:09 -05:00
5d6df46a2d fix(cpu): reimplement instruction handling
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-14 17:23:45 -05:00
7e65d82fef chore(cpu): document fetch, decode, execute 2021-08-14 16:42:38 -05:00
8c9567b610 chore(cpu): rename discriminants of ImeState enum 2021-08-14 16:42:15 -05:00
53dfaf0de2 fix(apu): increase size of the audio buffer
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-14 15:29:35 -05:00
16c2dd81fc fix(bus): remove dead code 2021-08-14 15:02:25 -05:00
79be38a1e6 fix(main): rename constant SCALE to WINDOW_SCALE
All checks were successful
continuous-integration/drone/push Build is passing
2021-08-14 01:02:18 -05:00
8625bec059 feat: clock bus on instruction read-write
Some checks failed
continuous-integration/drone/push Build is failing
Commit also includes general work towards passing mem-timings.

Note: while cpu_instrs.gb passes, instr_timing.gb and mem_timing.gb both
are stuck in infinite loops (Currently, it seems like a timing issue).
This is a major regression that hopefully shouldn't last for too long.
2021-08-14 00:10:51 -05:00
0637b771e3 chore(instr): implement copy and clone on instruction enum
Some checks failed
continuous-integration/drone/push Build is failing
2021-08-05 20:04:39 -05:00
0107fa04c9 chore(apu): remove implemtation of register that always returns 0xFF 2021-08-05 16:39:04 -05:00
6265c8af04 chore(joypad): poll input every frame instead of every instruction
Some checks failed
continuous-integration/drone/push Build is failing
2021-08-03 20:36:55 -05:00
5482a8e75f fix(apu): fix out of bounds error with channel 3 wave ram 2021-08-03 20:35:22 -05:00
002dae6826 fix(joypad): improve handling of keyboard input 2021-08-03 20:23:43 -05:00
c863dc835c fix(apu): fix index out of bounds error on channel 3 write 2021-08-03 20:23:08 -05:00
d4407cf849 fix(apu): implement NR50 volume controls 2021-08-03 19:33:27 -05:00
de0d147685 fix(cartridge): put a bit more detail into the MBC3 RTC stub
Some checks failed
continuous-integration/drone/push Build is failing
2021-08-03 15:19:08 -05:00
dd8339e8de chore(cartridge): consistently capitalize MBC 2021-08-03 15:06:06 -05:00
05d6475015 fix(cartridge): Use default title instead of empty string 2021-08-03 14:53:30 -05:00
32b597a328 fix(apu): incremental improvements to APU accuracy
Some checks failed
continuous-integration/drone/push Build is failing
2021-08-03 14:10:08 -05:00
33be2e0e83 fix(main): tie emulation to 60Hz on host machine 2021-08-03 14:09:16 -05:00
832e1b7633 fix(apu): implement WAVE RAM blocking 2021-08-02 22:38:00 -05:00
a549b9feef chore(bus): suppress warning 2021-08-02 21:55:07 -05:00
4d6fc95130 chore: remove premature optimizations 2021-08-02 21:52:12 -05:00
7112cd15e3 chore(cpu): refactor SM83 implementation
All checks were successful
continuous-integration/drone/push Build is passing
Instruction::decode no longer requires mutable access to the CPU
2021-07-31 20:29:13 -05:00
2af673d7c5 chore: remove various warnings
All checks were successful
continuous-integration/drone/push Build is passing
2021-07-28 16:25:29 -05:00
e43cac8041 fix(apu): don't spin if ringbuf is full 2021-07-28 16:23:31 -05:00
8724da824e chore: inline some methods 2021-07-28 14:53:52 -05:00
903cfacad3 fix(apu): replace mpsc with spsc ringbuffer
All checks were successful
continuous-integration/drone/push Build is passing
2021-07-28 02:01:04 -05:00
9d0e099a97 chore: remove unnecessary derivations from structs 2021-07-28 00:09:07 -05:00
d9a3a7b0dd chore: update dependencies
All checks were successful
continuous-integration/drone/push Build is passing
2021-07-27 22:52:58 -05:00
1a4546f4a4 chore: remove egui from project
I want to re-implement this later.
2021-07-27 22:38:21 -05:00
bd685a99a4 chore: update dependencies 2021-07-27 22:30:00 -05:00
22 changed files with 3242 additions and 3759 deletions

8
.vscode/extensions.json vendored Normal file
View File

@@ -0,0 +1,8 @@
{
"recommendations": [
"matklad.rust-analyzer",
"tamasfe.even-better-toml",
"serayuzgur.crates",
"vadimcn.vscode-lldb",
]
}

984
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@@ -3,6 +3,7 @@ name = "gb"
version = "0.1.0"
authors = ["Rekai Musuka <rekai@musuka.dev>"]
edition = "2018"
resolver = "2"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -11,14 +12,11 @@ anyhow = "^1.0"
bitfield = "^0.13"
clap = "^2.33"
gilrs = "^0.8"
pixels = "^0.3"
winit = "^0.24"
winit_input_helper = "^0.9"
egui = "^0.10"
egui_wgpu_backend = { git="https://github.com/hasenbanck/egui_wgpu_backend.git", rev="9d03ad345d15d1e44165849b242d3562fdf3e859" }
egui_winit_platform = { git="https://github.com/hasenbanck/egui_winit_platform.git", rev="17298250e9721e8bf2c1d4a17b3e22777f8cb2e8" }
pixels = "^0.6"
winit = "^0.25"
winit_input_helper = "^0.10"
rodio = "^0.14"
crossbeam-channel = "^0.5"
rtrb = "^0.1.4"
[profile.release]
debug = true

File diff suppressed because it is too large Load Diff

View File

@@ -1,47 +1,83 @@
use super::{AUDIO_BUFFER_LEN, CHANNEL_COUNT, SAMPLE_RATE};
use crossbeam_channel::{Receiver, SendError, Sender};
use rodio::Source;
use std::collections::VecDeque;
use rtrb::{Consumer, Producer, PushError, RingBuffer};
pub struct AudioMPSC;
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
impl AudioMPSC {
pub fn init() -> (AudioSender<f32>, AudioReceiver<f32>) {
// TODO: Can we provide an upper limit for this?
// The larger this channel is, the more lag there is between the Audio and
// Emulator
let (send, recv) = crossbeam_channel::unbounded();
pub struct AudioSPSC<T> {
inner: RingBuffer<T>,
}
(AudioSender { inner: send }, AudioReceiver { inner: recv })
impl<T> Default for AudioSPSC<T> {
fn default() -> Self {
Self {
inner: RingBuffer::new(BUFFER_CAPACITY),
}
}
}
#[derive(Debug, Clone)]
pub struct AudioSender<T> {
inner: Sender<T>,
}
impl<T> AudioSPSC<T> {
pub fn new(capacity: usize) -> Self {
Self {
inner: RingBuffer::new(capacity),
}
}
impl<T> AudioSender<T> {
pub(crate) fn send_samples(&self, left: T, right: T) -> Result<(), SendError<T>> {
self.inner.send(left).and(self.inner.send(right))?;
Ok(())
pub fn init(self) -> (SampleProducer<T>, SampleConsumer<T>) {
let (prod, cons) = self.inner.split();
(
SampleProducer { inner: prod },
SampleConsumer { inner: cons },
)
}
}
pub struct AudioReceiver<T> {
inner: Receiver<T>,
pub struct SampleProducer<T> {
inner: Producer<T>,
}
impl<T> Iterator for AudioReceiver<T> {
type Item = T;
impl<T> SampleProducer<T> {
pub(crate) fn push(&mut self, value: T) -> Result<(), PushError<T>> {
self.inner.push(value)
}
#[allow(dead_code)]
pub(crate) fn available(&self) -> bool {
self.inner.slots() > 2
}
pub(crate) fn available_blocking(&self) -> bool {
loop {
if self.inner.slots() > 2 {
break true;
}
}
}
}
impl<T> std::fmt::Debug for SampleProducer<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct(&format!("SampleProducer<{}>", std::any::type_name::<T>()))
.finish_non_exhaustive()
}
}
pub struct SampleConsumer<T> {
inner: Consumer<T>,
}
impl Iterator for SampleConsumer<f32> {
type Item = f32;
fn next(&mut self) -> Option<Self::Item> {
// TODO: Should this never return none?
self.inner.recv().ok()
// As of 2021-07-28, PopError can only be Empty
Some(self.inner.pop().unwrap_or_default())
}
}
impl<T: rodio::Sample> Source for AudioReceiver<T> {
impl Source for SampleConsumer<f32> {
fn current_frame_len(&self) -> Option<usize> {
// A frame changes when the samples rate or
// number of channels change. This will never happen, so
@@ -63,30 +99,3 @@ impl<T: rodio::Sample> Source for AudioReceiver<T> {
None
}
}
#[derive(Debug, Clone)]
pub(crate) struct AudioBuffer<T> {
inner: VecDeque<T>,
}
impl<T> Default for AudioBuffer<T> {
fn default() -> Self {
Self {
inner: VecDeque::with_capacity(AUDIO_BUFFER_LEN * CHANNEL_COUNT),
}
}
}
impl<T> AudioBuffer<T> {
pub(crate) fn push_back(&mut self, value: T) {
self.inner.push_back(value)
}
pub(crate) fn pop_front(&mut self) -> Option<T> {
self.inner.pop_front()
}
pub(crate) fn len(&self) -> usize {
self.inner.len()
}
}

View File

@@ -73,7 +73,7 @@ pub(crate) mod ch1 {
}
}
pub(crate) mod ch3 {
pub(super) mod ch3 {
#[derive(Debug, Clone, Copy)]
pub(crate) enum Volume {
Mute = 0,
@@ -102,7 +102,7 @@ pub(crate) mod ch3 {
}
}
pub(crate) mod ch4 {
pub(super) mod ch4 {
use super::bitfield;
bitfield! {
@@ -178,12 +178,12 @@ pub(crate) mod ch4 {
pub struct Frequency(u8);
impl Debug;
_initial, _: 7;
_idk, _: 6; // TODO: same as FrequencyHigh, figure out what this is
_length_disable, _: 6;
}
impl Frequency {
pub(crate) fn idk(&self) -> bool {
self._idk()
pub(crate) fn length_disable(&self) -> bool {
self._length_disable()
}
pub(crate) fn initial(&self) -> bool {
@@ -217,14 +217,14 @@ pub(crate) mod ch4 {
}
}
pub(crate) mod common {
pub(super) mod common {
use super::bitfield;
bitfield! {
pub struct FrequencyHigh(u8);
impl Debug;
_initial, _: 7;
_idk, _: 6; // TODO: Figure out what the hell this is
_length_disable, _: 6;
pub freq_bits, set_freq_bits: 2, 0;
}
@@ -233,8 +233,8 @@ pub(crate) mod common {
self._initial()
}
pub(crate) fn idk(&self) -> bool {
self._idk()
pub(crate) fn length_disable(&self) -> bool {
self._length_disable()
}
}
@@ -342,7 +342,7 @@ pub(crate) mod common {
pub struct SoundDuty(u8);
impl Debug;
from into WavePattern, _wave_pattern, _: 7, 6;
_sound_length, _: 5, 0; // TODO: Getter only used if bit 6 in NR14 is set
_sound_length, _: 5, 0;
}
impl SoundDuty {
@@ -427,41 +427,6 @@ pub(crate) mod common {
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum FrameSequencerState {
Step0Length,
Step1Nothing,
Step2LengthAndSweep,
Step3Nothing,
Step4Length,
Step5Nothing,
Step6LengthAndSweep,
Step7VolumeEnvelope,
}
impl FrameSequencerState {
pub(crate) fn step(&mut self) {
use FrameSequencerState::*;
*self = match *self {
Step0Length => Step1Nothing,
Step1Nothing => Step2LengthAndSweep,
Step2LengthAndSweep => Step3Nothing,
Step3Nothing => Step4Length,
Step4Length => Step5Nothing,
Step5Nothing => Step6LengthAndSweep,
Step6LengthAndSweep => Step7VolumeEnvelope,
Step7VolumeEnvelope => Step0Length,
};
}
}
impl Default for FrameSequencerState {
fn default() -> Self {
Self::Step0Length
}
}
bitfield! {
pub struct SoundOutput(u8);
impl Debug;
@@ -475,6 +440,24 @@ bitfield! {
pub ch1_right, _: 0;
}
impl SoundOutput {
pub(super) fn ch1(&self) -> (bool, bool) {
(self.ch1_left(), self.ch1_right())
}
pub(super) fn ch2(&self) -> (bool, bool) {
(self.ch2_left(), self.ch2_right())
}
pub(super) fn ch3(&self) -> (bool, bool) {
(self.ch3_left(), self.ch3_right())
}
pub(super) fn ch4(&self) -> (bool, bool) {
(self.ch4_left(), self.ch4_right())
}
}
impl Copy for SoundOutput {}
impl Clone for SoundOutput {
fn clone(&self) -> Self {
@@ -503,10 +486,20 @@ impl From<SoundOutput> for u8 {
bitfield! {
pub struct ChannelControl(u8);
impl Debug;
vin_so2, _: 7;
so2_level, _: 6, 4;
vin_so1, _: 3;
so1_level, _: 2, 0;
vin_left, _: 7;
_left_volume, _: 6, 4;
vin_right, _: 3;
_right_volume, _: 2, 0;
}
impl ChannelControl {
pub(crate) fn left_volume(&self) -> f32 {
self._left_volume() as f32
}
pub(crate) fn right_volume(&self) -> f32 {
self._right_volume() as f32
}
}
impl Copy for ChannelControl {}
@@ -533,3 +526,52 @@ impl From<ChannelControl> for u8 {
ctrl.0
}
}
pub(super) mod fs {
#[derive(Debug)]
pub(crate) struct FrameSequencer {
step: u8,
state: State,
}
impl Default for FrameSequencer {
fn default() -> Self {
Self {
step: Default::default(),
state: State::Length,
}
}
}
impl FrameSequencer {
pub(crate) fn next(&mut self) {
use State::*;
self.step = (self.step + 1) % 8;
self.state = match self.step {
1 | 3 | 5 => Nothing,
0 | 4 => Length,
2 | 6 => LengthAndSweep,
7 => Envelope,
_ => unreachable!("Step {} is invalid for the Frame Sequencer", self.step),
};
}
pub(crate) fn state(&self) -> State {
self.state
}
pub(crate) fn reset(&mut self) {
self.step = Default::default();
self.state = State::Length;
}
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum State {
Length,
Nothing,
LengthAndSweep,
Envelope,
}
}

View File

@@ -7,14 +7,13 @@ 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, Clone)]
#[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
cart: Option<Cartridge>,
pub(crate) ppu: Ppu,
work_ram: WorkRam,
var_ram: VariableWorkRam,
@@ -30,7 +29,7 @@ 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(),
@@ -45,44 +44,41 @@ impl Default for Bus {
}
impl Bus {
pub(crate) fn with_boot(path: &str) -> anyhow::Result<Self> {
let mut file = File::open(path)?;
let mut boot_rom = [0u8; 256];
file.read_exact(&mut boot_rom)?;
Ok(Self {
boot: Some(boot_rom),
pub(crate) fn with_boot(rom: [u8; 256]) -> Self {
Self {
boot: Some(rom),
..Default::default()
})
}
}
pub(crate) fn load_cartridge(&mut self, path: &str) -> std::io::Result<()> {
self.cartridge = Some(Cartridge::new(path)?);
Ok(())
pub(crate) fn load_cart(&mut self, rom: Vec<u8>) {
self.cart = Some(Cartridge::new(rom));
}
pub(crate) fn rom_title(&self) -> Option<&str> {
self.cartridge.as_ref()?.title()
pub(crate) fn cart_title(&self) -> Option<&str> {
self.cart.as_ref()?.title()
}
pub(crate) fn apu(&self) -> &Apu {
&self.apu
}
pub(crate) fn apu_mut(&mut self) -> &mut Apu {
&mut self.apu
#[allow(dead_code)]
pub(crate) fn boot_mapped(&self) -> bool {
self.boot.is_some()
}
pub(crate) fn clock(&mut self) {
self.ppu.clock();
self.timer.clock();
self.apu.clock(self.timer.divider);
self.clock_dma();
self.tick(4);
}
fn clock_dma(&mut self) {
if let Some((src_addr, dest_addr)) = self.ppu.dma.clock() {
fn tick(&mut self, limit: u8) {
for _ in 0..limit {
self.timer.tick();
self.ppu.tick();
self.apu.tick(self.timer.divider);
self.dma_tick()
}
}
fn dma_tick(&mut self) {
if let Some((src_addr, dest_addr)) = self.ppu.dma.tick() {
let byte = self.oam_read_byte(src_addr);
self.oam_write_byte(dest_addr, byte);
}
@@ -101,13 +97,13 @@ impl Bus {
}
}
match self.cartridge.as_ref() {
match self.cart.as_ref() {
Some(cart) => cart.read_byte(addr),
None => panic!("Tried to read from a non-existent cartridge"),
}
}
0x8000..=0x9FFF => self.ppu.read_byte(addr), // 8KB Video RAM
0xA000..=0xBFFF => match self.cartridge.as_ref() {
0xA000..=0xBFFF => match self.cart.as_ref() {
// 8KB External RAM
Some(cart) => cart.read_byte(addr),
None => panic!("Tried to read from a non-existent cartridge"),
@@ -154,7 +150,7 @@ impl BusIo for Bus {
}
}
match self.cartridge.as_ref() {
match self.cart.as_ref() {
Some(cart) => cart.read_byte(addr),
None => panic!("Tried to read from a non-existent cartridge"),
}
@@ -166,7 +162,7 @@ impl BusIo for Bus {
_ => self.ppu.read_byte(addr),
}
}
0xA000..=0xBFFF => match self.cartridge.as_ref() {
0xA000..=0xBFFF => match self.cart.as_ref() {
// 8KB External RAM
Some(cart) => cart.read_byte(addr),
None => panic!("Tried to read from a non-existent cartridge"),
@@ -220,7 +216,7 @@ impl BusIo for Bus {
0x01 => self.serial.next,
0x02 => self.serial.ctrl.into(),
0x04 => (self.timer.divider >> 8) as u8,
0x05 => self.timer.counter,
0x05 => self.timer.tima(),
0x06 => self.timer.modulo,
0x07 => self.timer.ctrl.into(),
0x0F => self.interrupt_flag().into(),
@@ -237,7 +233,6 @@ impl BusIo for Bus {
0x49 => self.ppu.monochrome.obj_palette_1.into(),
0x4A => self.ppu.pos.window_y,
0x4B => self.ppu.pos.window_x,
0x4D => 0xFF, // TODO: CGB Specific Register
_ => {
eprintln!("Read 0xFF from unused IO register {:#06X}.", addr);
0xFF
@@ -260,7 +255,7 @@ impl BusIo for Bus {
0x0000..=0x7FFF => {
// 16KB ROM bank 00 (ends at 0x3FFF)
// and 16KB ROM Bank 01 -> NN (switchable via MB)
match self.cartridge.as_mut() {
match self.cart.as_mut() {
Some(cart) => cart.write_byte(addr, byte),
None => panic!("Tried to write into non-existent cartridge"),
}
@@ -274,7 +269,7 @@ impl BusIo for Bus {
}
0xA000..=0xBFFF => {
// 8KB External RAM
match self.cartridge.as_mut() {
match self.cart.as_mut() {
Some(cart) => cart.write_byte(addr, byte),
None => panic!("Tried to write into non-existent cartridge"),
}
@@ -309,7 +304,7 @@ impl BusIo for Bus {
_ => {}
}
}
0xFEA0..=0xFEFF => {} // TODO: As far as I know, writes to here do nothing.
0xFEA0..=0xFEFF => {} // FIXME: As far as I know, writes to here do nothing.
0xFF00..=0xFF7F => {
// IO Registers
@@ -320,7 +315,7 @@ impl BusIo for Bus {
0x01 => self.serial.next = byte,
0x02 => self.serial.ctrl = byte.into(),
0x04 => self.timer.divider = 0x0000,
0x05 => self.timer.counter = byte,
0x05 => self.timer.set_tima(byte),
0x06 => self.timer.modulo = byte,
0x07 => self.timer.ctrl = byte.into(),
0x0F => self.set_interrupt_flag(byte),
@@ -371,17 +366,6 @@ impl BusIo for Bus {
}
}
impl Bus {
pub(crate) fn read_word(&self, addr: u16) -> u16 {
(self.read_byte(addr + 1) as u16) << 8 | self.read_byte(addr) as u16
}
pub(crate) fn write_word(&mut self, addr: u16, word: u16) {
self.write_byte(addr + 1, (word >> 8) as u8);
self.write_byte(addr, (word & 0x00FF) as u8);
}
}
impl Bus {
fn interrupt_flag(&self) -> InterruptFlag {
// Read the current interrupt information from the PPU

View File

@@ -1,137 +1,99 @@
use std::fs::File;
use std::io::{self, Read};
use std::path::Path;
use crate::bus::BusIo;
const RAM_SIZE_ADDRESS: usize = 0x0149;
const ROM_SIZE_ADDRESS: usize = 0x0148;
const MBC_TYPE_ADDRESS: usize = 0x0147;
const ROM_TITLE_RANGE: std::ops::RangeInclusive<usize> = 0x0134..=0x0143;
#[derive(Debug, Clone, Default)]
#[derive(Debug, Default)]
pub(crate) struct Cartridge {
memory: Vec<u8>,
title: Option<String>,
mbc: Box<dyn MemoryBankController>,
mbc: Box<dyn MBCIo>,
}
impl Cartridge {
pub(crate) fn new<P: AsRef<Path> + ?Sized>(path: &P) -> io::Result<Self> {
let mut memory = vec![];
let mut rom = File::open(path)?;
rom.read_to_end(&mut memory)?;
pub(crate) fn new(memory: Vec<u8>) -> Self {
let title = Self::find_title(&memory);
eprintln!("Cartridge Title: {:?}", title);
Ok(Self {
Self {
mbc: Self::detect_mbc(&memory),
title,
memory,
})
}
}
fn detect_mbc(memory: &[u8]) -> Box<dyn MemoryBankController> {
let ram_size = Self::find_ram_size(memory);
let bank_count = Self::find_bank_count(memory);
fn detect_mbc(memory: &[u8]) -> Box<dyn MBCIo> {
let ram_size = Self::detect_ram_info(memory);
let rom_size = Self::detect_rom_info(memory);
let mbc_kind = Self::find_mbc(memory);
let ram_byte_count = ram_size.len();
let ram_cap = ram_size.capacity();
let rom_cap = rom_size.capacity();
eprintln!("Cartridge Ram Size: {} bytes", ram_size.len());
eprintln!("Cartridge ROM Size: {} bytes", bank_count.size());
eprintln!("Cartridge Ram Size: {} bytes", ram_cap);
eprintln!("Cartridge ROM Size: {} bytes", rom_size.capacity());
eprintln!("MBC Type: {:?}", mbc_kind);
match mbc_kind {
MbcKind::None => Box::new(NoMbc {}),
MbcKind::Mbc1 => {
let mbc = Mbc1 {
ram_size,
ram: vec![0; ram_byte_count as usize],
bank_count,
..Default::default()
};
Box::new(mbc)
}
MbcKind::Mbc1WithBattery => {
// TODO: Implement Saving
let mbc = Mbc1 {
ram_size,
ram: vec![0; ram_byte_count as usize],
bank_count,
..Default::default()
};
Box::new(mbc)
}
MbcKind::Mbc3WithBattery => {
// TODO: Implement Saving
let mbc = MBC3 {
ram_size,
ram: vec![0; ram_byte_count as usize],
..Default::default()
};
Box::new(mbc)
}
MbcKind::Mbc3 => {
let mbc = MBC3 {
ram_size,
ram: vec![0; ram_byte_count as usize],
..Default::default()
};
Box::new(mbc)
}
MbcKind::Mbc5 => todo!("Implement MBC5"),
MBCKind::None => Box::new(NoMBC),
MBCKind::MBC1 => Box::new(MBC1::new(ram_size, rom_size)),
MBCKind::MBC1WithBattery => Box::new(MBC1::new(ram_size, rom_size)), // TODO: Implement Saving
MBCKind::MBC2 => Box::new(MBC2::new(rom_cap)),
MBCKind::MBC2WithBattery => Box::new(MBC2::new(rom_cap)), // TODO: Implement Saving
MBCKind::MBC3 => Box::new(MBC3::new(ram_cap)),
MBCKind::MBC3WithBattery => Box::new(MBC3::new(ram_cap)), // TODO: Implement Saving
MBCKind::MBC5 => Box::new(MBC5::new(ram_cap, rom_cap)),
MBCKind::MBC5WithBattery => Box::new(MBC5::new(ram_cap, rom_cap)), // TDO: Implement Saving
}
}
fn find_title(memory: &[u8]) -> Option<String> {
// FIXME: Get rid of magic values and handle cases
// where 0x134..0x143 reads past the length of the
// string
let slice = &memory[ROM_TITLE_RANGE];
let with_nulls = std::str::from_utf8(slice).ok();
let trimmed = with_nulls.map(|s| s.trim_matches('\0').trim());
let slice = &memory[0x134..0x143];
let str_with_nulls = std::str::from_utf8(slice).ok();
str_with_nulls.map(|s| s.trim_matches('\0').to_string())
match trimmed {
Some("") | None => None,
Some(_) => trimmed.map(String::from),
}
}
pub(crate) fn title(&self) -> Option<&str> {
self.title.as_deref()
}
fn find_ram_size(memory: &[u8]) -> RamSize {
fn detect_ram_info(memory: &[u8]) -> RamSize {
let id = memory[RAM_SIZE_ADDRESS];
id.into()
}
fn find_bank_count(memory: &[u8]) -> BankCount {
fn detect_rom_info(memory: &[u8]) -> RomSize {
let id = memory[ROM_SIZE_ADDRESS];
id.into()
}
fn find_mbc(memory: &[u8]) -> MbcKind {
let id = memory[MBC_TYPE_ADDRESS];
fn find_mbc(memory: &[u8]) -> MBCKind {
use MBCKind::*;
// TODO: Refactor this to match the other enums in this module
match id {
0x00 => MbcKind::None,
0x01 => MbcKind::Mbc1,
0x02 => MbcKind::Mbc1,
0x03 => MbcKind::Mbc1WithBattery,
0x19 => MbcKind::Mbc5,
0x13 => MbcKind::Mbc3WithBattery,
0x11 => MbcKind::Mbc3,
_ => unimplemented!("id {:#04X} is an unsupported MBC", id),
match memory[MBC_TYPE_ADDRESS] {
0x00 => None,
0x01 | 0x02 => MBC1,
0x03 => MBC1WithBattery,
0x05 => MBC2,
0x06 => MBC2WithBattery,
0x19 | 0x1A => MBC5,
0x1B => MBC5WithBattery,
0x13 => MBC3WithBattery,
0x11 | 0x12 => MBC3,
id => unimplemented!("id {:#04X} is an unsupported MBC", id),
}
}
}
impl BusIo for Cartridge {
fn read_byte(&self, addr: u16) -> u8 {
use MbcResult::*;
use MBCResult::*;
match self.mbc.handle_read(addr) {
Address(addr) => self.memory[addr],
@@ -144,92 +106,90 @@ impl BusIo for Cartridge {
}
}
#[derive(Debug, Clone)]
struct Mbc1 {
#[derive(Debug)]
struct MBC1 {
/// 5-bit number
rom_bank: u8,
/// 2-bit number
ram_bank: u8,
mode: bool,
ram_size: RamSize,
ram: Vec<u8>,
bank_count: BankCount,
ram_enabled: bool,
memory: Vec<u8>,
rom_size: RomSize,
mem_enabled: bool,
}
impl Default for Mbc1 {
fn default() -> Self {
impl MBC1 {
fn new(ram_size: RamSize, rom_size: RomSize) -> Self {
Self {
rom_bank: 0x01,
memory: vec![0; ram_size.capacity() as usize],
ram_size,
rom_size,
ram_bank: Default::default(),
mode: Default::default(),
ram_size: Default::default(),
ram: Default::default(),
bank_count: Default::default(),
ram_enabled: Default::default(),
mem_enabled: Default::default(),
}
}
}
impl Mbc1 {
fn zero_bank(&self) -> u8 {
use BankCount::*;
use RomSize::*;
match self.bank_count {
match self.rom_size {
None | Four | Eight | Sixteen | ThirtyTwo => 0x00,
SixtyFour => (self.ram_bank & 0x01) << 5,
OneHundredTwentyEight => (self.ram_bank & 0x03) << 5,
_ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count),
OneTwentyEight => (self.ram_bank & 0x03) << 5,
_ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size),
}
}
fn _mbcm_zero_bank(&self) -> u8 {
use BankCount::*;
use RomSize::*;
match self.bank_count {
match self.rom_size {
None | Four | Eight | Sixteen | ThirtyTwo => 0x00,
SixtyFour => (self.ram_bank & 0x03) << 4,
OneHundredTwentyEight => (self.ram_bank & 0x03) << 5,
_ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count),
OneTwentyEight => (self.ram_bank & 0x03) << 5,
_ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size),
}
}
fn high_bank(&self) -> u8 {
use BankCount::*;
use RomSize::*;
let base = self.rom_bank & self.rom_size_mask();
match self.bank_count {
match self.rom_size {
None | Four | Eight | Sixteen | ThirtyTwo => base,
SixtyFour => base & !(0x01 << 5) | ((self.ram_bank & 0x01) << 5),
OneHundredTwentyEight => base & !(0x03 << 5) | ((self.ram_bank & 0x03) << 5),
_ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count),
OneTwentyEight => base & !(0x03 << 5) | ((self.ram_bank & 0x03) << 5),
_ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size),
}
}
fn rom_size_mask(&self) -> u8 {
use BankCount::*;
use RomSize::*;
match self.bank_count {
match self.rom_size {
None => 0b00000001,
Four => 0b00000011,
Eight => 0b00000111,
Sixteen => 0b00001111,
ThirtyTwo | SixtyFour | OneHundredTwentyEight => 0b00011111,
_ => unreachable!("{:?} is not a valid MBC1 BankCount", self.bank_count),
ThirtyTwo | SixtyFour | OneTwentyEight => 0b00011111,
_ => unreachable!("{:?} is not a valid MBC1 BankCount", self.rom_size),
}
}
fn ram_addr(&self, addr: u16) -> u16 {
fn ram_addr(&self, addr: u16) -> usize {
use RamSize::*;
match self.ram_size {
_2KB | _8KB => (addr - 0xA000) % self.ram_size.len() as u16,
_32KB => {
Unused | One => (addr as usize - 0xA000) % self.ram_size.capacity(),
Four => {
if self.mode {
0x2000 * self.ram_bank as u16 + (addr - 0xA000)
0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)
} else {
addr - 0xA000
addr as usize - 0xA000
}
}
_ => unreachable!("RAM size can not be greater than 32KB on MBC1"),
@@ -237,49 +197,39 @@ impl Mbc1 {
}
}
impl MemoryBankController for Mbc1 {
fn handle_read(&self, addr: u16) -> MbcResult {
use MbcResult::*;
impl MBCIo for MBC1 {
fn handle_read(&self, addr: u16) -> MBCResult {
use MBCResult::*;
match addr {
0x0000..=0x3FFF => {
if self.mode {
Address(0x4000 * self.zero_bank() as usize + addr as usize)
} else {
Address(addr as usize)
}
0x0000..=0x3FFF if self.mode => {
Address(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.ram_enabled {
Value(self.ram[self.ram_addr(addr) as usize])
} else {
Value(0xFF)
}
0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => {
Value(self.memory[self.ram_addr(addr)])
}
0xA000..=0xBFFF => Value(0xFF),
_ => unreachable!("A read from {:#06X} should not be handled by MBC1", addr),
}
}
fn handle_write(&mut self, addr: u16, byte: u8) {
match addr {
0x0000..=0x1FFF => self.ram_enabled = (byte & 0x0F) == 0x0A,
0x0000..=0x1FFF => self.mem_enabled = (byte & 0x0F) == 0x0A,
0x2000..=0x3FFF => {
self.rom_bank = if byte == 0x00 {
0x01
} else {
byte & self.rom_size_mask()
};
self.rom_bank &= 0x1F;
let value = byte & 0x1F;
let masked_value = byte & self.rom_size_mask();
self.rom_bank = if value == 0 { 0x01 } else { masked_value };
}
0x4000..=0x5FFF => self.ram_bank = byte & 0x03,
0x6000..=0x7FFF => self.mode = (byte & 0x01) == 0x01,
0xA000..=0xBFFF if self.ram_enabled => {
let ram_addr = self.ram_addr(addr) as usize;
self.ram[ram_addr] = byte;
0xA000..=0xBFFF if self.mem_enabled && self.ram_size != RamSize::None => {
let ram_addr = self.ram_addr(addr);
self.memory[ram_addr] = byte;
}
0xA000..=0xBFFF => {} // Ram isn't enabled, ignored write
_ => unreachable!("A write to {:#06X} should not be handled by MBC1", addr),
@@ -293,32 +243,47 @@ enum MBC3Device {
RealTimeClock,
}
#[derive(Debug, Clone, Default)]
#[derive(Debug)]
struct MBC3 {
/// 7-bit Number
rom_bank: u8,
/// 2-bit Number
ram_bank: u8,
devices_enabled: bool,
currently_mapped: Option<MBC3Device>,
ram_size: RamSize,
ram: Vec<u8>,
devs_enabled: bool,
mapped: Option<MBC3Device>,
memory: Vec<u8>,
// RTC Data Latch Previous Write
prev_latch_write: Option<u8>,
}
impl MemoryBankController for MBC3 {
fn handle_read(&self, addr: u16) -> MbcResult {
use MbcResult::*;
impl MBC3 {
fn new(ram_cap: usize) -> Self {
Self {
memory: vec![0; ram_cap],
rom_bank: Default::default(),
ram_bank: Default::default(),
devs_enabled: Default::default(),
mapped: Default::default(),
prev_latch_write: Default::default(),
}
}
}
impl MBCIo for MBC3 {
fn handle_read(&self, addr: u16) -> MBCResult {
use MBCResult::*;
let res = match addr {
0x0000..=0x3FFF => Address(addr as usize),
0x4000..=0x7FFF => Address(0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)),
0xA000..=0xBFFF => match self.currently_mapped {
Some(MBC3Device::ExternalRam) if self.devices_enabled => {
Value(self.ram[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
0xA000..=0xBFFF => match self.mapped {
Some(MBC3Device::ExternalRam) if self.devs_enabled => {
Value(self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
}
Some(MBC3Device::RealTimeClock) if self.devices_enabled => {
unimplemented!("Reading from MBC3 RTC is currently unsupported")
Some(MBC3Device::RealTimeClock) if self.devs_enabled => {
todo!("Return Latched value of register")
}
_ => Value(0xFF),
},
@@ -330,29 +295,37 @@ impl MemoryBankController for MBC3 {
fn handle_write(&mut self, addr: u16, byte: u8) {
match addr {
0x000..=0x1FFF => self.devices_enabled = (byte & 0x0F) == 0x0A, // Enable External RAM and Access to RTC if there is one
0x2000..=0x3FFF => match byte {
0x00 => self.rom_bank = 0x01,
byte => self.rom_bank = byte & 0x7F,
},
0x000..=0x1FFF => self.devs_enabled = (byte & 0x0F) == 0x0A, // Enable External RAM and Access to RTC if there is one
0x2000..=0x3FFF => {
self.rom_bank = match byte {
0x00 => 0x01,
byte => byte & 0x7F,
}
}
0x4000..=0x5FFF => match byte {
0x00 | 0x01 | 0x02 | 0x03 => {
self.ram_bank = byte & 0x03;
self.currently_mapped = Some(MBC3Device::ExternalRam);
self.mapped = Some(MBC3Device::ExternalRam);
}
0x08 | 0x09 | 0x0A | 0x0B | 0x0C => {
self.currently_mapped = Some(MBC3Device::RealTimeClock);
unimplemented!("RTC in MBC3 is currently unimplemented")
self.mapped = Some(MBC3Device::RealTimeClock);
}
_ => {}
},
0x6000..=0x7FFF => unimplemented!("RTC Data Latch is currently unimplemented"),
0xA000..=0xBFFF => match self.currently_mapped {
Some(MBC3Device::ExternalRam) if self.devices_enabled => {
self.ram[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte
0x6000..=0x7FFF => {
if let Some(0x00) = self.prev_latch_write {
if byte == 0x01 {
todo!("Perform Data Latch")
}
}
Some(MBC3Device::RealTimeClock) if self.devices_enabled => {
unimplemented!("Writing to MBC3 RTC is currently unsupported")
self.prev_latch_write = Some(byte);
}
0xA000..=0xBFFF => match self.mapped {
Some(MBC3Device::ExternalRam) if self.devs_enabled => {
self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte
}
Some(MBC3Device::RealTimeClock) if self.devs_enabled => {
todo!("Write to RTC")
}
_ => {}
},
@@ -361,12 +334,132 @@ impl MemoryBankController for MBC3 {
}
}
#[derive(Debug, Clone, Copy)]
struct NoMbc {}
#[derive(Debug)]
struct MBC5 {
/// 9-bit number
rom_bank: u16,
/// 4-bit number
ram_bank: u8,
impl MemoryBankController for NoMbc {
fn handle_read(&self, addr: u16) -> MbcResult {
MbcResult::Address(addr as usize)
rom_cap: usize,
memory: Vec<u8>,
mem_enabled: bool,
}
impl MBC5 {
fn new(ram_cap: usize, rom_cap: usize) -> Self {
Self {
rom_bank: 0x01,
memory: vec![0; ram_cap],
rom_cap,
ram_bank: Default::default(),
mem_enabled: Default::default(),
}
}
fn bank_addr(&self, addr: u16) -> usize {
(0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)) % self.rom_cap
}
}
impl MBCIo for MBC5 {
fn handle_read(&self, addr: u16) -> MBCResult {
use MBCResult::*;
match addr {
0x0000..=0x3FFF => Address(addr as usize),
0x4000..=0x7FFF => Address(self.bank_addr(addr)),
0xA000..=0xBFFF if self.mem_enabled => {
Value(self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)])
}
0xA000..=0xBFFF => Value(0xFF),
_ => unreachable!("A read from {:#06X} should not be handled by MBC5", addr),
}
}
fn handle_write(&mut self, addr: u16, byte: u8) {
match addr {
0x0000..=0x1FFF => self.mem_enabled = (byte & 0x0F) == 0x0A,
0x2000..=0x2FFF => self.rom_bank = (self.rom_bank & 0x0100) | byte as u16,
0x3000..=0x3FFF => self.rom_bank = (self.rom_bank & 0x00FF) | (byte as u16 & 0x01) << 8,
0x4000..=0x5FFF => self.ram_bank = byte & 0x0F,
0xA000..=0xBFFF if self.mem_enabled => {
self.memory[0x2000 * self.ram_bank as usize + (addr as usize - 0xA000)] = byte;
}
0xA000..=0xBFFF => {}
_ => unreachable!("A write to {:#06X} should not be handled by MBC5", addr),
}
}
}
#[derive(Debug)]
struct MBC2 {
/// 4-bit number
rom_bank: u8,
memory: Box<[u8; Self::RAM_SIZE]>,
mem_enabled: bool,
rom_cap: usize,
}
impl MBC2 {
const RAM_SIZE: usize = 0x0200;
fn new(rom_cap: usize) -> Self {
Self {
rom_bank: 0x01,
memory: Box::new([0; Self::RAM_SIZE]),
mem_enabled: Default::default(),
rom_cap,
}
}
fn rom_addr(&self, addr: u16) -> usize {
(0x4000 * self.rom_bank as usize + (addr as usize - 0x4000)) % self.rom_cap
}
}
impl MBCIo for MBC2 {
fn handle_read(&self, addr: u16) -> MBCResult {
use MBCResult::*;
match addr {
0x0000..=0x3FFF => Address(addr as usize),
0x4000..=0x7FFF => Address(self.rom_addr(addr)),
0xA000..=0xBFFF if self.mem_enabled => {
let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1);
Value(self.memory[mbc2_addr] | 0xF0)
}
0xA000..=0xBFFF => Value(0xFF),
_ => unreachable!("A read from {:#06X} should not be handled by MBC2", addr),
}
}
fn handle_write(&mut self, addr: u16, byte: u8) {
let nybble = byte & 0x0F;
match addr {
0x0000..=0x3FFF if addr >> 8 & 0x01 == 0x01 => {
self.rom_bank = if nybble == 0x00 { 0x01 } else { nybble };
}
0x0000..=0x3FFF => self.mem_enabled = nybble == 0x0A,
0xA000..=0xBFFF if self.mem_enabled => {
let mbc2_addr = addr as usize & (Self::RAM_SIZE - 1);
self.memory[mbc2_addr] = nybble;
}
0x4000..=0x7FFF | 0xA000..=0xBFFF => {}
_ => unreachable!("A write to {:#06X} should not be handled by MBC2", addr),
}
}
}
#[derive(Debug)]
struct NoMBC;
impl MBCIo for NoMBC {
fn handle_read(&self, addr: u16) -> MBCResult {
MBCResult::Address(addr as usize)
}
fn handle_write(&mut self, _addr: u16, _byte: u8) {
@@ -374,67 +467,57 @@ impl MemoryBankController for NoMbc {
}
}
trait MemoryBankController: CloneMbc {
fn handle_read(&self, addr: u16) -> MbcResult;
trait MBCIo {
fn handle_read(&self, addr: u16) -> MBCResult;
fn handle_write(&mut self, addr: u16, byte: u8);
}
trait CloneMbc {
fn clone_mbc(&self) -> Box<dyn MemoryBankController>;
}
impl<T> CloneMbc for T
where
T: MemoryBankController + Clone + 'static,
{
fn clone_mbc<'a>(&self) -> Box<dyn MemoryBankController> {
Box::new(self.clone())
}
}
#[derive(Debug, Clone, Copy)]
enum MbcResult {
enum MBCResult {
Address(usize),
Value(u8),
}
#[derive(Debug, Clone, Copy)]
enum MbcKind {
enum MBCKind {
None,
Mbc1,
Mbc1WithBattery,
Mbc5,
Mbc3WithBattery,
Mbc3,
MBC1,
MBC1WithBattery,
MBC2,
MBC2WithBattery,
MBC3,
MBC3WithBattery,
MBC5,
MBC5WithBattery,
}
impl Default for MbcKind {
impl Default for MBCKind {
fn default() -> Self {
Self::None
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
enum RamSize {
None = 0x00,
_2KB = 0x01,
_8KB = 0x02,
_32KB = 0x03, // Split into 4 RAM banks
_128KB = 0x04, // Split into 16 RAM banks
_64KB = 0x05, // Split into 8 RAm Banks
Unused = 0x01,
One = 0x02,
Four = 0x03,
Sixteen = 0x04,
Eight = 0x05,
}
impl RamSize {
fn len(&self) -> u32 {
fn capacity(&self) -> usize {
use RamSize::*;
match *self {
None => 0,
_2KB => 2_048,
_8KB => 8_192,
_32KB => 32_768,
_128KB => 131_072,
_64KB => 65_536,
Unused => 0x800,
One => 0x2000,
Four => 0x8000,
Sixteen => 0x20000,
Eight => 0x10000,
}
}
}
@@ -451,63 +534,49 @@ impl From<u8> for RamSize {
match byte {
0x00 => None,
0x01 => _2KB,
0x02 => _8KB,
0x03 => _32KB,
0x04 => _128KB,
0x05 => _64KB,
0x01 => Unused,
0x02 => One,
0x03 => Four,
0x04 => Sixteen,
0x05 => Eight,
_ => unreachable!("{:#04X} is not a valid value for RAMSize", byte),
}
}
}
#[derive(Debug, Clone, Copy)]
enum BankCount {
None = 0x00, // 32KB (also called Two)
Four = 0x01, // 64KB
Eight = 0x02, // 128KB
Sixteen = 0x03, // 256KB
ThirtyTwo = 0x04, // 512KB
SixtyFour = 0x05, // 1MB
OneHundredTwentyEight = 0x06, // 2MB
TwoHundredFiftySix = 0x07, // 4MB
FiveHundredTwelve = 0x08, // 8MB
SeventyTwo = 0x52, // 1.1MB
Eighty = 0x53, // 1.2MB
NinetySix = 0x54, // 1.5MB
enum RomSize {
None = 0x00, // 32KB (also called Two)
Four = 0x01, // 64KB
Eight = 0x02, // 128KB
Sixteen = 0x03, // 256KB
ThirtyTwo = 0x04, // 512KB
SixtyFour = 0x05, // 1MB
OneTwentyEight = 0x06, // 2MB
TwoFiftySix = 0x07, // 4MB
FiveHundredTwelve = 0x08, // 8MB
SeventyTwo = 0x52, // 1.1MB
Eighty = 0x53, // 1.2MB
NinetySix = 0x54, // 1.5MB
}
impl Default for BankCount {
fn default() -> Self {
Self::None
}
}
impl BankCount {
impl RomSize {
// https://hacktix.github.io/GBEDG/mbcs/#rom-size
fn size(self) -> u32 {
use BankCount::*;
fn capacity(&self) -> usize {
use RomSize::*;
match self {
None => 32_768,
Four => 65_536,
Eight => 131_072,
Sixteen => 262_144,
ThirtyTwo => 524_288,
SixtyFour => 1_048_576,
OneHundredTwentyEight => 2_097_152,
TwoHundredFiftySix => 4_194_304,
FiveHundredTwelve => 8_388_608,
SeventyTwo => 1_179_648,
Eighty => 1_310_720,
NinetySix => 1_572_864,
SeventyTwo => 0x120000,
Eighty => 0x140000,
NinetySix => 0x180000,
_ => 0x8000 << *self as u8,
}
}
}
impl From<u8> for BankCount {
impl From<u8> for RomSize {
fn from(byte: u8) -> Self {
use BankCount::*;
use RomSize::*;
match byte {
0x00 => None,
@@ -516,8 +585,8 @@ impl From<u8> for BankCount {
0x03 => Sixteen,
0x04 => ThirtyTwo,
0x05 => SixtyFour,
0x06 => OneHundredTwentyEight,
0x07 => TwoHundredFiftySix,
0x06 => OneTwentyEight,
0x07 => TwoFiftySix,
0x08 => FiveHundredTwelve,
0x52 => SeventyTwo,
0x53 => Eighty,
@@ -527,20 +596,14 @@ impl From<u8> for BankCount {
}
}
impl std::fmt::Debug for Box<dyn MemoryBankController> {
impl std::fmt::Debug for Box<dyn MBCIo> {
fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
todo!("Implement Debug for Box<dyn MBC> Trait Object");
}
}
impl std::clone::Clone for Box<dyn MemoryBankController> {
fn clone(&self) -> Self {
self.clone_mbc()
}
}
impl Default for Box<dyn MemoryBankController> {
impl Default for Box<dyn MBCIo> {
fn default() -> Self {
Box::new(Mbc1::default())
Box::new(NoMBC)
}
}

View File

@@ -1,26 +1,21 @@
use crate::apu::Apu;
use crate::bus::{Bus, BusIo};
use crate::instruction::{Cycle, Instruction};
use crate::bus::{Bus, BusIo, BOOT_SIZE};
use crate::instruction::Instruction;
use crate::interrupt::{InterruptEnable, InterruptFlag};
use crate::joypad::Joypad;
use crate::ppu::Ppu;
use crate::timer::Timer;
use crate::Cycle;
use bitfield::bitfield;
use std::fmt::{Display, Formatter, Result as FmtResult};
#[derive(Debug, Clone, Default)]
#[derive(Debug, Default)]
pub struct Cpu {
pub bus: Bus,
reg: Registers,
flags: Flags,
ime: ImeState,
// TODO: Merge halted and state properties
halted: Option<HaltState>,
state: State,
}
impl Cpu {
pub fn new() -> Self {
pub(crate) fn without_boot() -> Self {
Self {
reg: Registers {
a: 0x01,
@@ -38,11 +33,11 @@ impl Cpu {
}
}
pub fn boot_new(path: &str) -> anyhow::Result<Self> {
Ok(Self {
bus: Bus::with_boot(path)?,
pub(crate) fn with_boot(rom: [u8; BOOT_SIZE]) -> Self {
Self {
bus: Bus::with_boot(rom),
..Default::default()
})
}
}
pub(crate) fn ime(&self) -> ImeState {
@@ -53,88 +48,100 @@ impl Cpu {
self.ime = state;
}
pub(crate) fn halt(&mut self, state: HaltState) {
self.halted = Some(state);
pub(crate) fn halt_cpu(&mut self, kind: HaltKind) {
self.state = State::Halt(kind);
}
fn resume(&mut self) {
self.halted = None;
fn resume_execution(&mut self) {
self.state = State::Execute;
}
pub(crate) fn halted(&self) -> Option<HaltState> {
self.halted
pub(crate) fn is_halted(&self) -> bool {
match self.state {
State::Halt(_) => true,
_ => false,
}
}
fn inc_pc(&mut self) {
self.reg.pc += 1;
}
pub 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()
pub(crate) fn halt_kind(&self) -> Option<HaltKind> {
match self.state {
State::Halt(kind) => Some(kind),
_ => None,
}
}
}
impl Cpu {
fn fetch(&self) -> u8 {
self.bus.read_byte(self.reg.pc)
/// Fetch an [Instruction] from the memory bus
/// (4 cycles)
fn fetch(&mut self) -> u8 {
let byte = self.read_byte(self.reg.pc);
self.bus.clock();
self.reg.pc += 1;
byte
}
/// Decode a byte into an [SM83](Cpu) [Instruction]
///
/// If opcode == 0xCB, then decoding costs 4 cycles.
/// Otherwise, decoding is free
pub(crate) fn decode(&mut self, opcode: u8) -> Instruction {
Instruction::from_byte(self, opcode)
if opcode == 0xCB {
Instruction::decode(self.fetch(), true)
} else {
Instruction::decode(opcode, false)
}
}
/// Execute an [Instruction].
///
/// The amount of cycles necessary to execute an instruction range from
/// 0 to 20 T-cycles
fn execute(&mut self, instruction: Instruction) -> Cycle {
Instruction::execute(self, instruction)
}
/// Perform the [`Cpu::fetch()`] [`Cpu::decode()`] [`Cpu::execute()`]
/// routine.
///
/// Handle HALT and interrupts.
pub fn step(&mut self) -> Cycle {
let cycles = match self.halted() {
Some(state) => {
use HaltState::*;
// Log instructions
// if self.reg.pc > 0xFF {
// let out = std::io::stdout();
// let _ = self._print_logs(out.lock());
// }
match state {
ImeEnabled | NonePending => Cycle::new(4),
SomePending => todo!("Implement HALT bug"),
}
}
None => {
let opcode = self.fetch();
self.inc_pc();
let instr = self.decode(opcode);
// let out = std::io::stdout();
// let _ = self._debug_log(out.lock(), &instr);
let cycles = self.execute(instr);
self.check_ime();
cycles
}
};
// TODO: With how we currently handle audio
// this --while being correct-- incurs a performance penalty
// as our emu is audio-bound.
let mut elapsed = 0x00;
let pending: u32 = cycles.into();
while elapsed < pending {
if !self.bus.apu().is_full() {
self.bus.clock();
elapsed += 1;
} else {
self.bus.apu_mut().flush_samples();
}
if let Some(elapsed) = self.handle_interrupt() {
return elapsed;
}
self.handle_interrupts();
if let Some(kind) = self.halt_kind() {
use HaltKind::*;
cycles
self.bus.clock();
let elapsed = match kind {
ImeEnabled | NonePending => 4,
SomePending => todo!("Implement HALT bug"),
};
return elapsed;
}
let opcode = self.fetch();
let instr = self.decode(opcode);
let elapsed = self.execute(instr);
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);
}
elapsed
}
}
@@ -149,128 +156,108 @@ impl BusIo for Cpu {
}
impl Cpu {
pub(crate) fn read_imm_byte(&mut self, addr: u16) -> u8 {
self.inc_pc(); // NB: the addr read in the line below will be equal to PC - 1 after this function call
self.bus.read_byte(addr)
pub(crate) fn bus(&self) -> &Bus {
&self.bus
}
pub(crate) fn read_imm_word(&mut self, addr: u16) -> u16 {
self.inc_pc();
self.inc_pc(); // NB: the addr read in the line below will be equal to PC - 2 after this function call
self.bus.read_word(addr)
pub(crate) fn bus_mut(&mut self) -> &mut Bus {
&mut self.bus
}
pub(crate) fn write_word(&mut self, addr: u16, word: u16) {
self.bus.write_word(addr, word)
}
}
impl Cpu {
pub fn ppu(&mut self) -> &Ppu {
&self.bus.ppu
}
pub fn apu_mut(&mut self) -> &mut Apu {
&mut self.bus.apu
}
pub(crate) fn joypad_mut(&mut self) -> &mut Joypad {
&mut self.bus.joypad
}
pub(crate) fn timer(&self) -> &Timer {
&self.bus.timer
}
fn check_ime(&mut self) {
fn handle_ei(&mut self) {
match self.ime {
ImeState::Pending => {
// This is within the context of the EI instruction, we need to not update EI until the end of the
// next executed Instruction
self.ime = ImeState::PendingEnd;
}
ImeState::PendingEnd => {
// The Instruction after EI has now been executed, so we want to enable the IME flag here
self.ime = ImeState::Enabled;
}
ImeState::Disabled | ImeState::Enabled => {} // Do Nothing
ImeState::EiExecuted => self.ime = ImeState::Pending,
ImeState::Pending => self.ime = ImeState::Enabled,
ImeState::Disabled | ImeState::Enabled => {}
}
}
fn handle_interrupts(&mut self) {
let req = self.read_byte(0xFF0F);
let enabled = self.read_byte(0xFFFF);
pub(crate) fn int_request(&self) -> u8 {
self.read_byte(0xFF0F)
}
if self.halted.is_some() {
pub(crate) fn int_enable(&self) -> u8 {
self.read_byte(0xFFFF)
}
fn handle_interrupt(&mut self) -> Option<Cycle> {
let irq = self.int_request();
let enable = self.int_enable();
// TODO: Ensure that this behaviour is correct
if self.is_halted() {
// When we're here either a HALT with IME set or
// a HALT with IME not set and No pending Interrupts was called
if req & enabled != 0 {
if irq & enable != 0 {
// The if self.ime() below correctly follows the "resuming from HALT" behaviour so
// nothing actually needs to be added here. This is just documentation
// since it's a bit weird why nothing is being done
self.resume()
self.resume_execution();
}
}
if let ImeState::Enabled = self.ime() {
let mut req: InterruptFlag = req.into();
let enabled: InterruptEnable = enabled.into();
match self.ime() {
ImeState::Enabled => {
let mut irq: InterruptFlag = irq.into();
let enable: InterruptEnable = enable.into();
let vector = if req.vblank() && enabled.vblank() {
// Handle VBlank Interrupt
req.set_vblank(false);
let rst_vector = if irq.vblank() && enable.vblank() {
// Handle VBlank Interrupt
irq.set_vblank(false);
// INT 40h
Some(0x40)
} else if req.lcd_stat() && enabled.lcd_stat() {
// Handle LCD STAT Interrupt
req.set_lcd_stat(false);
// INT 40h
Some(0x40)
} else if irq.lcd_stat() && enable.lcd_stat() {
// Handle LCD STAT Interrupt
irq.set_lcd_stat(false);
// INT 48h
Some(0x48)
} else if req.timer() && enabled.timer() {
// Handle Timer Interrupt
req.set_timer(false);
// INT 48h
Some(0x48)
} else if irq.timer() && enable.timer() {
// Handle Timer Interrupt
irq.set_timer(false);
// INT 50h
Some(0x50)
} else if req.serial() && enabled.serial() {
// Handle Serial Interrupt
req.set_serial(false);
// INT 50h
Some(0x50)
} else if irq.serial() && enable.serial() {
// Handle Serial Interrupt
irq.set_serial(false);
// INT 58h
Some(0x58)
} else if req.joypad() && enabled.joypad() {
// Handle Joypad Interrupt
req.set_joypad(false);
// INT 58h
Some(0x58)
} else if irq.joypad() && enable.joypad() {
// Handle Joypad Interrupt
irq.set_joypad(false);
// INT 60h
Some(0x60)
} else {
None
};
// INT 60h
Some(0x60)
} else {
None
};
let _ = match vector {
Some(address) => {
// Write the Changes to 0xFF0F and 0xFFFF registers
self.write_byte(0xFF0F, req.into());
match rst_vector {
Some(vector) => {
// Write the Changes to 0xFF0F and 0xFFFF registers
self.write_byte(0xFF0F, irq.into());
// Disable all future interrupts
self.set_ime(ImeState::Disabled);
Instruction::reset(self, address)
// Disable all future interrupts
self.set_ime(ImeState::Disabled);
Some(Instruction::reset(self, vector))
}
None => None,
}
None => Cycle::new(0), // NO Interrupts were enabled and / or requested
};
}
_ => None,
}
}
}
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Clone, Copy)]
enum State {
Execute,
// Halt,
Halt(HaltKind),
// Stop,
}
@@ -292,7 +279,6 @@ impl Cpu {
E => self.reg.e = value,
H => self.reg.h = value,
L => self.reg.l = value,
Flag => self.flags = value.into(),
}
}
@@ -307,7 +293,6 @@ impl Cpu {
E => self.reg.e,
H => self.reg.h,
L => self.reg.l,
Flag => self.flags.into(),
}
}
@@ -356,13 +341,20 @@ impl Cpu {
&self.flags
}
pub(crate) fn update_flags(&mut self, z: bool, n: bool, h: bool, c: bool) {
self.flags.set_z(z);
self.flags.set_n(n);
self.flags.set_h(h);
self.flags.set_c(c);
}
pub(crate) fn set_flags(&mut self, flags: Flags) {
self.flags = flags;
}
}
impl Cpu {
fn _debug_log(&self, mut w: impl std::io::Write, instr: &Instruction) -> std::io::Result<()> {
fn _print_debug(&self, mut w: impl std::io::Write) -> std::io::Result<()> {
write!(w, "A: {:02X} ", self.reg.a)?;
write!(w, "F: {:02X} ", u8::from(self.flags))?;
write!(w, "B: {:02X} ", self.reg.b)?;
@@ -376,31 +368,40 @@ impl Cpu {
write!(w, "({:02X} ", self.read_byte(self.reg.pc))?;
write!(w, "{:02X} ", self.read_byte(self.reg.pc + 1))?;
write!(w, "{:02X} ", self.read_byte(self.reg.pc + 2))?;
write!(w, "{:02X}) ", self.read_byte(self.reg.pc + 3))?;
writeln!(w, "| {:?}", instr)?;
write!(w, "{:02X})", self.read_byte(self.reg.pc + 3))?;
writeln!(w, "| {:?}", self._dbg_instr())?;
w.flush()
}
fn _log_state(&self, mut writer: impl std::io::Write) -> std::io::Result<()> {
write!(writer, "A: {:02X} ", self.reg.a)?;
write!(writer, "F: {:02X} ", u8::from(self.flags))?;
write!(writer, "B: {:02X} ", self.reg.b)?;
write!(writer, "C: {:02X} ", self.reg.c)?;
write!(writer, "D: {:02X} ", self.reg.d)?;
write!(writer, "E: {:02X} ", self.reg.e)?;
write!(writer, "H: {:02X} ", self.reg.h)?;
write!(writer, "L: {:02X} ", self.reg.l)?;
write!(writer, "SP: {:04X} ", self.reg.sp)?;
write!(writer, "PC: 00:{:04X} ", self.reg.pc)?;
write!(writer, "({:02X} ", self.read_byte(self.reg.pc))?;
write!(writer, "{:02X} ", self.read_byte(self.reg.pc + 1))?;
write!(writer, "{:02X} ", self.read_byte(self.reg.pc + 2))?;
writeln!(writer, "{:02X})", self.read_byte(self.reg.pc + 3))?;
writer.flush()
fn _print_logs(&self, mut w: impl std::io::Write) -> std::io::Result<()> {
write!(w, "A: {:02X} ", self.reg.a)?;
write!(w, "F: {:02X} ", u8::from(self.flags))?;
write!(w, "B: {:02X} ", self.reg.b)?;
write!(w, "C: {:02X} ", self.reg.c)?;
write!(w, "D: {:02X} ", self.reg.d)?;
write!(w, "E: {:02X} ", self.reg.e)?;
write!(w, "H: {:02X} ", self.reg.h)?;
write!(w, "L: {:02X} ", self.reg.l)?;
write!(w, "SP: {:04X} ", self.reg.sp)?;
write!(w, "PC: 00:{:04X} ", self.reg.pc)?;
write!(w, "({:02X} ", self.read_byte(self.reg.pc))?;
write!(w, "{:02X} ", self.read_byte(self.reg.pc + 1))?;
write!(w, "{:02X} ", self.read_byte(self.reg.pc + 2))?;
writeln!(w, "{:02X})", self.read_byte(self.reg.pc + 3))?;
w.flush()
}
fn _dbg_instr(&self) -> Instruction {
let byte = self.read_byte(self.reg.pc);
if byte == 0xCB {
Instruction::decode(self.read_byte(self.reg.pc + 1), true)
} else {
Instruction::decode(byte, false)
}
}
}
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Clone, Copy)]
pub(crate) enum Register {
A,
B,
@@ -409,10 +410,9 @@ pub(crate) enum Register {
E,
H,
L,
Flag,
}
#[derive(Debug, Copy, Clone)]
#[derive(Debug, Clone, Copy)]
pub(crate) enum RegisterPair {
AF,
BC,
@@ -422,7 +422,7 @@ pub(crate) enum RegisterPair {
PC,
}
#[derive(Debug, Copy, Clone, Default)]
#[derive(Debug, Default)]
struct Registers {
a: u8,
b: u8,
@@ -507,7 +507,7 @@ impl From<u8> for Flags {
}
#[derive(Debug, Clone, Copy)]
pub(crate) enum HaltState {
pub(crate) enum HaltKind {
ImeEnabled,
NonePending,
SomePending,
@@ -516,8 +516,8 @@ pub(crate) enum HaltState {
#[derive(Debug, Clone, Copy)]
pub(crate) enum ImeState {
Disabled,
EiExecuted,
Pending,
PendingEnd,
Enabled,
}

View File

@@ -1,60 +1,121 @@
use crate::cpu::Cpu as SM83;
use crate::instruction::Cycle;
use crate::joypad;
use crate::ppu::Ppu;
use anyhow::Result;
use crate::apu::gen::SampleProducer;
use crate::cpu::Cpu;
use crate::joypad::{self, Joypad};
use crate::{Cycle, GB_HEIGHT, GB_WIDTH};
use gilrs::Gilrs;
use std::time::Duration;
use winit_input_helper::WinitInputHelper;
pub const SM83_CYCLE_TIME: Duration = Duration::from_nanos(1_000_000_000 / SM83_CLOCK_SPEED);
pub const CYCLES_IN_FRAME: Cycle = Cycle::new(456 * 154); // 456 Cycles times 154 scanlines
pub const CYCLES_IN_FRAME: Cycle = 456 * 154; // 456 Cycles times 154 scanlines
pub(crate) const SM83_CLOCK_SPEED: u64 = 0x40_0000; // Hz which is 4.194304Mhz
const DEFAULT_TITLE: &str = "DMG-01 Emulator";
const GAMEPAD_ENABLED: bool = false;
pub fn init(boot_path: Option<&str>, rom_path: &str) -> Result<SM83> {
let mut cpu = match boot_path {
Some(path) => SM83::boot_new(path)?,
None => SM83::new(),
};
pub fn run_frame(emu: &mut Emulator, gamepad: &mut Gilrs, key: &WinitInputHelper) -> Cycle {
let mut elapsed = 0;
eprintln!("Initialized GB Emulator");
if let Some(event) = gamepad.next_event() {
joypad::handle_gamepad_input(emu.joyp_mut(), event);
}
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,
pending: Cycle,
) -> Cycle {
let mut elapsed = Cycle::new(0);
while elapsed < pending {
elapsed += run_unsynced(game_boy, gamepad, input);
joypad::handle_keyboard_input(emu.joyp_mut(), key);
while elapsed < CYCLES_IN_FRAME {
elapsed += emu.step();
}
elapsed
}
pub fn run_unsynced(game_boy: &mut SM83, gamepad: &mut Gilrs, input: &WinitInputHelper) -> Cycle {
if GAMEPAD_ENABLED {
if let Some(event) = gamepad.next_event() {
joypad::handle_gamepad_input(game_boy.joypad_mut(), event);
pub fn draw_frame(emu: &Emulator, buf: &mut [u8; GB_HEIGHT * GB_WIDTH * 4]) {
buf.copy_from_slice(emu.cpu.bus().ppu.frame_buf());
}
pub struct Emulator {
cpu: Cpu,
timestamp: Cycle,
}
impl Emulator {
fn new(cpu: Cpu) -> Self {
Self {
cpu,
timestamp: Default::default(),
}
}
joypad::handle_keyboard_input(game_boy.joypad_mut(), input);
game_boy.step()
fn step(&mut self) -> Cycle {
self.cpu.step()
}
fn load_cart(&mut self, rom: Vec<u8>) {
self.cpu.bus_mut().load_cart(rom)
}
fn joyp_mut(&mut self) -> &mut Joypad {
&mut self.cpu.bus_mut().joypad
}
pub fn set_prod(&mut self, prod: SampleProducer<f32>) {
self.cpu.bus_mut().apu.attach_producer(prod)
}
pub fn title(&self) -> &str {
self.cpu.bus().cart_title().unwrap_or(DEFAULT_TITLE)
}
}
pub fn draw(ppu: &Ppu, frame: &mut [u8]) {
ppu.copy_to_gui(frame);
pub mod build {
use std::fs::File;
use std::io::{Read, Result};
use std::path::Path;
use crate::bus::BOOT_SIZE;
use crate::cpu::Cpu;
use super::Emulator;
#[derive(Debug, Default)]
pub struct EmulatorBuilder {
boot: Option<[u8; BOOT_SIZE]>,
cart: Option<Vec<u8>>,
}
impl EmulatorBuilder {
pub fn new() -> Self {
Default::default()
}
pub fn with_boot<P: AsRef<Path>>(mut self, path: P) -> Result<Self> {
let mut file = File::open(path.as_ref())?;
let mut buf = [0x00; BOOT_SIZE];
file.read_exact(&mut buf)?;
self.boot = Some(buf);
Ok(self)
}
pub fn with_cart<P: AsRef<Path>>(mut self, path: P) -> Result<Self> {
let mut file = File::open(path.as_ref())?;
let mut buf = Vec::new();
file.read_to_end(&mut buf)?;
self.cart = Some(buf);
Ok(self)
}
pub fn finish(mut self) -> Emulator {
let mut emu = Emulator::new(match self.boot {
Some(rom) => Cpu::with_boot(rom),
None => Cpu::without_boot(),
});
if let Some(rom) = self.cart.take() {
emu.load_cart(rom)
}
emu
}
}
}

View File

@@ -1,321 +0,0 @@
use crate::bus::BusIo;
use crate::cpu::{Cpu as SM83, Register, RegisterPair};
use egui::{ClippedMesh, FontDefinitions};
use egui_wgpu_backend::{RenderPass, ScreenDescriptor};
use egui_winit_platform::{Platform, PlatformDescriptor};
use pixels::{wgpu, PixelsContext};
use std::time::Instant;
use wgpu::TextureFormat::Bgra8UnormSrgb;
// Boilerplate code from: https://github.com/parasyte/pixels/blob/0.3.0/examples/egui-winit/src/gui.rs
/// Manages all state required for rendering egui over `Pixels`.
pub struct Egui {
// State for egui.
start_time: Instant,
platform: Platform,
screen_desc: ScreenDescriptor,
render_pass: RenderPass,
paint_jobs: Vec<ClippedMesh>,
pub config: Configuration,
show_flags: bool,
show_cpu_info: bool,
show_registers: bool,
show_int: bool,
show_timer: bool,
}
impl Egui {
/// Create egui.
pub fn new(width: u32, height: u32, scale_factor: f64, context: &PixelsContext) -> Self {
let platform = Platform::new(PlatformDescriptor {
physical_width: width,
physical_height: height,
scale_factor,
font_definitions: FontDefinitions::default(),
style: Default::default(),
});
let screen_desc = ScreenDescriptor {
physical_width: width,
physical_height: height,
scale_factor: scale_factor as f32,
};
let render_pass = RenderPass::new(&context.device, Bgra8UnormSrgb);
Self {
start_time: Instant::now(),
platform,
screen_desc,
render_pass,
paint_jobs: Vec::new(),
config: Default::default(),
show_flags: false,
show_cpu_info: false,
show_registers: false,
show_int: false,
show_timer: false,
}
}
/// Handle input events from the window manager.
pub fn handle_event(&mut self, event: &winit::event::Event<'_, ()>) {
self.platform.handle_event(event);
}
/// Resize egui.
pub fn resize(&mut self, width: u32, height: u32) {
self.screen_desc.physical_width = width;
self.screen_desc.physical_height = height;
}
/// Update scaling factor.
pub fn scale_factor(&mut self, scale_factor: f64) {
self.screen_desc.scale_factor = scale_factor as f32;
}
/// Prepare egui.
pub fn prepare(&mut self, game_boy: &SM83) {
self.platform
.update_time(self.start_time.elapsed().as_secs_f64());
// Begin the egui frame.
self.platform.begin_frame();
// Draw the demo application.
self.ui(&self.platform.context(), game_boy);
// End the egui frame and create all paint jobs to prepare for rendering.
let (_output, paint_commands) = self.platform.end_frame();
self.paint_jobs = self.platform.context().tessellate(paint_commands);
}
/// Create the UI using egui.
fn ui(&mut self, ctx: &egui::CtxRef, game_boy: &SM83) {
egui::TopPanel::top("menubar_container").show(ctx, |ui| {
egui::menu::bar(ui, |ui| {
egui::menu::menu(ui, "File", |ui| {
if ui.button("Configuration").clicked() {
self.config.show = true;
}
});
egui::menu::menu(ui, "Status", |ui| {
if ui.button("Flags").clicked() {
self.show_flags = true;
}
if ui.button("CPU Information").clicked() {
self.show_cpu_info = true;
}
if ui.button("Registers").clicked() {
self.show_registers = true;
}
if ui.button("Interrupts").clicked() {
self.show_int = true;
}
if ui.button("Timer").clicked() {
self.show_timer = true;
}
});
});
});
egui::Window::new("Cpu Flags")
.open(&mut self.show_flags)
.show(ctx, |ui| {
let flags = game_boy.flags();
ui.horizontal(|ui| {
let _ = ui.selectable_label(flags.z(), "Zero");
let _ = ui.selectable_label(flags.n(), "Negative");
let _ = ui.selectable_label(flags.h(), "Half-Carry");
let _ = ui.selectable_label(flags.c(), "Carry");
});
});
egui::Window::new("Timer")
.open(&mut self.show_timer)
.show(ctx, |ui| {
let timer = game_boy.timer();
ui.horizontal(|ui| {
ui.label("DIV");
ui.monospace(format!("{:#06X}", timer.divider));
});
ui.horizontal(|ui| {
ui.label("TIMA");
ui.monospace(format!("{:#04X}", timer.counter));
});
ui.horizontal(|ui| {
ui.label("TMA");
ui.monospace(format!("{:#04X}", timer.modulo));
});
ui.horizontal(|ui| {
ui.label("TAC");
ui.monospace(format!("{:?}", timer.ctrl));
});
});
egui::Window::new("Registers")
.open(&mut self.show_registers)
.show(ctx, |ui| {
ui.horizontal(|ui| {
ui.label("A");
ui.monospace(format!("{:#04X}", game_boy.register(Register::A)));
ui.label("F");
ui.monospace(format!("{:#04X}", game_boy.register(Register::Flag)));
});
ui.horizontal(|ui| {
ui.label("B");
ui.monospace(format!("{:#04X}", game_boy.register(Register::B)));
ui.label("C");
ui.monospace(format!("{:#04X}", game_boy.register(Register::C)));
});
ui.horizontal(|ui| {
ui.label("D");
ui.monospace(format!("{:#04X}", game_boy.register(Register::D)));
ui.label("E");
ui.monospace(format!("{:#04X}", game_boy.register(Register::E)));
});
ui.horizontal(|ui| {
ui.label("H");
ui.monospace(format!("{:#04X}", game_boy.register(Register::H)));
ui.label("L");
ui.monospace(format!("{:#04X}", game_boy.register(Register::L)));
});
ui.horizontal(|ui| {
ui.label("AF");
ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::AF)));
});
ui.horizontal(|ui| {
ui.label("BC");
ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::BC)));
});
ui.horizontal(|ui| {
ui.label("DE");
ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::DE)));
});
ui.horizontal(|ui| {
ui.label("HL");
ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::HL)));
});
});
egui::Window::new("Cpu Information")
.open(&mut self.show_cpu_info)
.show(ctx, |ui| {
ui.horizontal(|ui| {
ui.label("PC");
ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::PC)));
});
ui.horizontal(|ui| {
ui.label("SP");
ui.monospace(format!("{:#06X}", game_boy.register_pair(RegisterPair::SP)));
});
ui.horizontal(|ui| {
ui.label("IME");
ui.label(format!("{:?}", game_boy.ime()));
});
ui.horizontal(|ui| {
ui.label("HALT");
ui.label(format!("{:?}", game_boy.halted()));
});
});
egui::Window::new("IRQ Information")
.open(&mut self.show_int)
.show(ctx, |ui| {
let req = game_boy.read_byte(0xFF0F);
let enabled = game_boy.read_byte(0xFFFF);
ui.heading("Interrupt Requests");
ui.horizontal(|ui| {
let _ = ui.selectable_label(req & 0x01 == 0x01, "VBLANK");
let _ = ui.selectable_label((req >> 1) & 0x01 == 0x01, "LCD STAT");
let _ = ui.selectable_label((req >> 2) & 0x01 == 0x01, "TIMER");
let _ = ui.selectable_label((req >> 3) & 0x01 == 0x01, "SERIAL");
let _ = ui.selectable_label((req >> 4) & 0x01 == 0x01, "JOYPAD");
});
ui.heading("Interrupt Enable");
ui.horizontal(|ui| {
let _ = ui.selectable_label(enabled & 0x01 == 0x01, "VBLANK");
let _ = ui.selectable_label((enabled >> 1) & 0x01 == 0x01, "LCD STAT");
let _ = ui.selectable_label((enabled >> 2) & 0x01 == 0x01, "TIMER");
let _ = ui.selectable_label((enabled >> 3) & 0x01 == 0x01, "SERIAL");
let _ = ui.selectable_label((enabled >> 4) & 0x01 == 0x01, "JOYPAD");
});
});
egui::Window::new("Configuration")
.open(&mut self.config.show)
.show(ctx, |_ui| {});
}
/// Render egui.
pub fn render(
&mut self,
encoder: &mut wgpu::CommandEncoder,
render_target: &wgpu::TextureView,
context: &PixelsContext,
) {
// Upload all resources to the GPU.
self.render_pass.update_texture(
&context.device,
&context.queue,
&self.platform.context().texture(),
);
self.render_pass
.update_user_textures(&context.device, &context.queue);
self.render_pass.update_buffers(
&context.device,
&context.queue,
&self.paint_jobs,
&self.screen_desc,
);
// Record all render passes.
self.render_pass.execute(
encoder,
render_target,
&self.paint_jobs,
&self.screen_desc,
None,
);
}
}
pub struct Configuration {
/// Show Configuration egui menu
show: bool,
}
impl Default for Configuration {
fn default() -> Self {
Self { show: false }
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
use bitfield::bitfield;
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Default)]
pub(crate) struct Interrupt {
pub(crate) flag: InterruptFlag,
pub(crate) enable: InterruptEnable,

View File

@@ -1,7 +1,7 @@
use gilrs::{Button, Event as GamepadEvent, EventType as GamepadEventType};
use winit_input_helper::WinitInputHelper;
#[derive(Debug, Clone, Copy)]
#[derive(Debug)]
pub struct Joypad {
/// 0xFF00 | P1/JOYP - Player 1 Joypad
pub(crate) p1: u8,
@@ -48,7 +48,7 @@ impl Joypad {
}
}
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Default)]
struct JoypadState {
// Direction Row
dpad_down: ButtonEvent,
@@ -120,49 +120,57 @@ pub fn handle_keyboard_input(pad: &mut Joypad, input: &WinitInputHelper) {
if input.key_pressed(VirtualKeyCode::Down) {
state.dpad_down.update(true, irq);
} else if input.key_released(VirtualKeyCode::Down) {
}
if input.key_released(VirtualKeyCode::Down) {
state.dpad_down.update(false, irq);
}
if input.key_pressed(VirtualKeyCode::Up) {
state.dpad_up.update(true, irq);
} else if input.key_released(VirtualKeyCode::Up) {
}
if input.key_released(VirtualKeyCode::Up) {
state.dpad_up.update(false, irq);
}
if input.key_pressed(VirtualKeyCode::Left) {
state.dpad_left.update(true, irq);
} else if input.key_released(VirtualKeyCode::Left) {
}
if input.key_released(VirtualKeyCode::Left) {
state.dpad_left.update(false, irq);
}
if input.key_pressed(VirtualKeyCode::Right) {
state.dpad_right.update(true, irq);
} else if input.key_released(VirtualKeyCode::Right) {
}
if input.key_released(VirtualKeyCode::Right) {
state.dpad_right.update(false, irq);
}
if input.key_pressed(VirtualKeyCode::T) {
state.start.update(true, irq);
} else if input.key_released(VirtualKeyCode::T) {
}
if input.key_released(VirtualKeyCode::T) {
state.start.update(false, irq);
}
if input.key_pressed(VirtualKeyCode::Y) {
state.select.update(true, irq);
} else if input.key_released(VirtualKeyCode::Y) {
}
if input.key_released(VirtualKeyCode::Y) {
state.select.update(false, irq);
}
if input.key_pressed(VirtualKeyCode::Z) {
state.south.update(true, irq);
} else if input.key_released(VirtualKeyCode::Z) {
}
if input.key_released(VirtualKeyCode::Z) {
state.south.update(false, irq);
}
if input.key_pressed(VirtualKeyCode::X) {
state.east.update(true, irq);
} else if input.key_released(VirtualKeyCode::X) {
}
if input.key_released(VirtualKeyCode::X) {
state.east.update(false, irq);
}
}

View File

@@ -1,6 +1,5 @@
pub use apu::gen::AudioMPSC;
pub use gui::Egui;
pub use instruction::Cycle;
pub use apu::gen::AudioSPSC;
pub type Cycle = u64;
pub const GB_WIDTH: usize = 160;
pub const GB_HEIGHT: usize = 144;
@@ -10,7 +9,6 @@ mod bus;
mod cartridge;
mod cpu;
pub mod emu;
mod gui;
mod high_ram;
mod instruction;
mod interrupt;

View File

@@ -1,17 +1,20 @@
use std::convert::TryInto;
use anyhow::{anyhow, Result};
use clap::{crate_authors, crate_description, crate_name, crate_version, App, Arg};
use gb::{AudioMPSC, Cycle, Egui, GB_HEIGHT, GB_WIDTH};
use gb::emu::build::EmulatorBuilder;
use gb::{AudioSPSC, Cycle, GB_HEIGHT, GB_WIDTH};
use gilrs::Gilrs;
use pixels::{PixelsBuilder, SurfaceTexture};
use rodio::OutputStream;
use std::time::Instant;
use winit::dpi::LogicalSize;
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;
const SCALE: f64 = 2.0;
const WINDOW_SCALE: usize = 3;
const AUDIO_ENABLED: bool = false;
fn main() -> Result<()> {
let app = App::new(crate_name!())
@@ -38,70 +41,58 @@ fn main() -> Result<()> {
)
.get_matches();
// `rom` is a required value in every situation so this will
// always exist.
let rom_path = m
.value_of("rom")
.expect("Required value 'rom' was provided");
let mut emu_build =
EmulatorBuilder::new().with_cart(m.value_of("rom").expect("ROM path provided"))?;
let mut game_boy =
gb::emu::init(m.value_of("boot"), rom_path).expect("Initialized DMG-01 Emulator");
let cartridge_title = gb::emu::rom_title(&game_boy);
if let Some(path) = m.value_of("boot") {
emu_build = emu_build.with_boot(path)?;
}
// Initialize Gamepad Support
let mut gamepad = Gilrs::new().expect("Initialized Gilrs for Controller Input");
let mut emu = emu_build.finish();
let rom_title = emu.title();
let mut gamepad = Gilrs::new().expect("Initialize Controller Support");
// Initialize GUI
let event_loop = EventLoop::new();
let mut input = WinitInputHelper::new();
let window = create_window(&event_loop, cartridge_title)?;
let window = create_window(&event_loop, rom_title)?;
let (mut pixels, mut egui) = {
let mut pixels = {
let size = window.inner_size();
let scale_factor = window.scale_factor();
let surface_texture = SurfaceTexture::new(size.width, size.height, &window);
let pixels = PixelsBuilder::new(GB_WIDTH as u32, GB_HEIGHT as u32, surface_texture)
PixelsBuilder::new(GB_WIDTH as u32, GB_HEIGHT as u32, surface_texture)
.enable_vsync(false)
.build()?;
let egui = Egui::new(size.width, size.height, scale_factor, pixels.context());
(pixels, egui)
.build()?
};
let (send, recv) = AudioMPSC::init();
game_boy.apu_mut().set_audio_sender(send);
// Initialize Audio
let (_stream, stream_handle) = OutputStream::try_default().expect("Initialized Audio");
std::thread::spawn(move || {
stream_handle
.play_raw(recv)
.expect("Failed to play Audio Source");
});
if AUDIO_ENABLED {
let spsc: AudioSPSC<f32> = Default::default();
let (prod, cons) = spsc.init();
let sink = {
let s = Sink::try_new(&stream_handle)?;
s.append(cons);
s.set_volume(0.1);
s
};
emu.set_prod(prod);
std::thread::spawn(move || {
sink.sleep_until_end();
});
}
let mut now = Instant::now();
let mut cycle_count: Cycle = Default::default();
event_loop.run(move |event, _, control_flow| {
// Update egui
egui.handle_event(&event);
if let Event::RedrawRequested(_) = event {
// Prepare egui
egui.prepare(&game_boy);
// Render everything together
let render_result = pixels.render_with(|encoder, target, ctx| {
// Render the texture
ctx.scaling_renderer.render(encoder, target);
// Render egui
egui.render(encoder, target, ctx);
});
if render_result
if pixels
.render()
.map_err(|e| anyhow!("pixels.render() failed: {}", e))
.is_err()
{
@@ -116,57 +107,38 @@ fn main() -> Result<()> {
return;
}
if let Some(scale_factor) = input.scale_factor() {
egui.scale_factor(scale_factor);
}
if let Some(size) = input.window_resized() {
pixels.resize_surface(size.width, size.height);
egui.resize(size.width, size.height);
}
let delta = now.elapsed().subsec_nanos();
now = Instant::now();
let pending = Cycle::new(delta / gb::emu::SM83_CYCLE_TIME.subsec_nanos());
cycle_count += gb::emu::run(&mut game_boy, &mut gamepad, &input, pending);
cycle_count += gb::emu::run_frame(&mut emu, &mut gamepad, &input);
if cycle_count >= gb::emu::CYCLES_IN_FRAME {
// Draw Frame
cycle_count = Cycle::new(0);
cycle_count %= gb::emu::CYCLES_IN_FRAME;
gb::emu::draw(game_boy.ppu(), pixels.get_frame());
let buf: &mut [u8; GB_WIDTH * GB_HEIGHT * 4] = pixels
.get_frame()
.try_into()
.expect("Size of Pixel Buffer is GB_WIDTH * GB_HEIGHT * 4");
gb::emu::draw_frame(&emu, buf);
window.request_redraw();
}
}
});
}
#[cfg(not(windows))]
fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> {
let size = LogicalSize::new((GB_WIDTH as f64) * SCALE, (GB_HEIGHT as f64) * SCALE);
let logical = LogicalSize::new(GB_WIDTH as f64, GB_HEIGHT as f64);
let physical = PhysicalSize::new(
(GB_WIDTH * WINDOW_SCALE) as f32,
(GB_HEIGHT * WINDOW_SCALE) as f32,
);
Ok(WindowBuilder::new()
.with_title(title)
.with_inner_size(size)
.with_min_inner_size(size)
.with_min_inner_size(logical)
.with_inner_size(physical)
.with_resizable(true)
.with_decorations(true)
.with_transparent(false)
.build(event_loop)?)
}
#[cfg(windows)]
fn create_window(event_loop: &EventLoop<()>, title: &str) -> Result<Window> {
use winit::platform::windows::WindowBuilderExtWindows;
let size = LogicalSize::new((GB_WIDTH as f64) * SCALE, (GB_HEIGHT as f64) * SCALE);
Ok(WindowBuilder::new()
.with_title(title)
.with_inner_size(size)
.with_min_inner_size(size)
.with_resizable(true)
.with_decorations(true)
.with_transparent(false)
.with_drag_and_drop(false)
.build(event_loop)?)
}

View File

@@ -1,5 +1,5 @@
use crate::bus::BusIo;
use crate::instruction::Cycle;
use crate::Cycle;
use crate::GB_HEIGHT;
use crate::GB_WIDTH;
use dma::DirectMemoryAccess;
@@ -33,7 +33,7 @@ const LIGHT_GRAY: [u8; 4] = 0xAEBA89FFu32.to_be_bytes();
const DARK_GRAY: [u8; 4] = 0x5E6745FFu32.to_be_bytes();
const BLACK: [u8; 4] = 0x202020FFu32.to_be_bytes();
#[derive(Debug, Clone)]
#[derive(Debug)]
pub struct Ppu {
pub(crate) int: Interrupt,
/// 0xFF40 | LCDC - LCD Control
@@ -70,7 +70,7 @@ impl BusIo for Ppu {
}
impl Ppu {
pub(crate) fn clock(&mut self) {
pub(crate) fn tick(&mut self) {
self.cycle += 1;
if !self.ctrl.lcd_enabled() {
@@ -79,7 +79,7 @@ impl Ppu {
match self.stat.mode() {
PpuMode::OamScan => {
if self.cycle >= 80.into() {
if self.cycle >= 80 {
self.stat.set_mode(PpuMode::Drawing);
}
@@ -88,7 +88,7 @@ impl Ppu {
PpuMode::Drawing => {
if self.ctrl.lcd_enabled() {
// Only Draw when the LCD Is Enabled
self.draw(self.cycle.into());
self.draw(self.cycle);
} else {
self.reset();
}
@@ -125,7 +125,7 @@ impl Ppu {
PpuMode::HBlank => {
// This mode will always end at 456 cycles
if self.cycle >= 456.into() {
if self.cycle >= 456 {
self.cycle %= 456;
self.pos.line_y += 1;
@@ -167,7 +167,7 @@ impl Ppu {
}
}
PpuMode::VBlank => {
if self.cycle > 456.into() {
if self.cycle > 456 {
self.cycle %= 456;
self.pos.line_y += 1;
@@ -228,23 +228,26 @@ impl Ppu {
self.scan_state.next();
}
fn draw(&mut self, _cycle: u32) {
fn draw(&mut self, _cycle: Cycle) {
use FetcherState::*;
let iter = &mut self.obj_buffer.iter();
let mut iter = self.obj_buffer.iter_mut();
let default = &mut None;
let obj_attr = loop {
match iter.flatten().next() {
Some(attr) => {
if attr.x <= (self.x_pos + 8) {
self.fetch.back.reset();
self.fetch.back.pause();
self.fifo.pause();
match iter.next() {
Some(attr_opt) => {
if let Some(attr) = attr_opt {
if attr.x <= (self.x_pos + 8) {
self.fetch.back.reset();
self.fetch.back.pause();
self.fifo.pause();
break Some(*attr);
break attr_opt;
}
}
}
None => break None,
None => break default,
}
};
@@ -252,10 +255,10 @@ impl Ppu {
match self.fetch.obj.state {
TileNumber => {
self.fetch.obj.tile.with_id(attr.tile_index);
self.fetch.obj.next(ToLowByteSleep);
self.fetch.obj.next(SleepOne);
}
ToLowByteSleep => self.fetch.obj.next(TileLowByte),
TileLowByte => {
SleepOne => self.fetch.obj.next(TileLow),
TileLow => {
let obj_size = self.ctrl.obj_size();
let addr = PixelFetcher::get_obj_addr(&attr, &self.pos, obj_size);
@@ -263,10 +266,10 @@ impl Ppu {
let byte = self.read_byte(addr);
self.fetch.obj.tile.with_low_byte(byte);
self.fetch.obj.next(ToHighByteSleep);
self.fetch.obj.next(SleepTwo);
}
ToHighByteSleep => self.fetch.obj.next(TileHighByte),
TileHighByte => {
SleepTwo => self.fetch.obj.next(TileHigh),
TileHigh => {
let obj_size = self.ctrl.obj_size();
let addr = PixelFetcher::get_obj_addr(&attr, &self.pos, obj_size);
@@ -274,10 +277,10 @@ impl Ppu {
let byte = self.read_byte(addr + 1);
self.fetch.obj.tile.with_high_byte(byte);
self.fetch.obj.next(ToFifoSleep);
self.fetch.obj.next(SleepThree);
}
ToFifoSleep => self.fetch.obj.next(SendToFifoOne),
SendToFifoOne => {
SleepThree => self.fetch.obj.next(ToFifoOne),
ToFifoOne => {
// Load into Fifo
let (high, low) = self
.fetch
@@ -289,13 +292,12 @@ impl Ppu {
let tbpp = Pixels::from_bytes(high, low);
let palette_kind = attr.flags.palette();
let end = Pixels::PIXEL_COUNT - self.fifo.obj.len();
let start = Pixels::PIXEL_COUNT - end;
let x_flip = attr.flags.x_flip();
for i in start..Pixels::PIXEL_COUNT {
let pixel_count = (attr.x - self.x_pos) as usize;
let start = self.fifo.obj.len();
for i in start..pixel_count {
let x = if x_flip { 7 - i } else { i };
let priority = attr.flags.priority();
@@ -312,11 +314,11 @@ impl Ppu {
self.fetch.back.resume();
self.fifo.resume();
self.obj_buffer.remove(&attr);
let _ = std::mem::take(obj_attr);
self.fetch.obj.next(SendToFifoTwo);
self.fetch.obj.next(ToFifoTwo);
}
SendToFifoTwo => self.fetch.obj.reset(),
ToFifoTwo => self.fetch.obj.reset(),
}
}
@@ -346,31 +348,31 @@ impl Ppu {
self.fetch.back.tile.with_id(id);
// Move on to the Next state in 2 T-cycles
self.fetch.back.next(ToLowByteSleep);
self.fetch.back.next(SleepOne);
}
ToLowByteSleep => self.fetch.back.next(TileLowByte),
TileLowByte => {
SleepOne => self.fetch.back.next(TileLow),
TileLow => {
let addr = self.fetch.bg_byte_addr(&self.ctrl, &self.pos);
let low = self.read_byte(addr);
self.fetch.back.tile.with_low_byte(low);
self.fetch.back.next(ToHighByteSleep);
self.fetch.back.next(SleepTwo);
}
ToHighByteSleep => self.fetch.back.next(TileHighByte),
TileHighByte => {
SleepTwo => self.fetch.back.next(TileHigh),
TileHigh => {
let addr = self.fetch.bg_byte_addr(&self.ctrl, &self.pos);
let high = self.read_byte(addr + 1);
self.fetch.back.tile.with_high_byte(high);
self.fetch.back.next(ToFifoSleep);
self.fetch.back.next(SleepThree);
}
ToFifoSleep => self.fetch.back.next(SendToFifoOne),
SendToFifoOne => {
self.fetch.back.next(SendToFifoTwo);
SleepThree => self.fetch.back.next(ToFifoOne),
ToFifoOne => {
self.fetch.back.next(ToFifoTwo);
}
SendToFifoTwo => {
ToFifoTwo => {
if let Ok(()) = self.fetch.send_to_fifo(&mut self.fifo) {
self.fetch.x_pos += 1;
self.fetch.back.next(TileNumber);
@@ -381,12 +383,12 @@ impl Ppu {
}
if self.fifo.is_enabled() {
if self.x_pos == 0 && !self.fifo.back.is_empty() && self.scanline_start {
if self.x_pos == 0 && self.scanline_start {
self.to_discard = self.pos.scroll_x % 8;
self.scanline_start = false;
}
if self.to_discard > 0 {
if self.to_discard > 0 && !self.fifo.back.is_empty() {
let _ = self.fifo.back.pop_front();
self.to_discard -= 1;
@@ -416,8 +418,8 @@ impl Ppu {
self.frame_buf.swap_with_slice(&mut blank);
}
pub fn copy_to_gui(&self, frame: &mut [u8]) {
frame.copy_from_slice(self.frame_buf.as_ref());
pub(crate) fn frame_buf(&self) -> &[u8; GB_HEIGHT * GB_WIDTH * 4] {
&self.frame_buf
}
fn clock_fifo(&mut self) -> Option<GrayShade> {
@@ -468,7 +470,7 @@ impl Default for Ppu {
fn default() -> Self {
Self {
vram: Box::new([0u8; VRAM_SIZE]),
cycle: Cycle::new(0),
cycle: Default::default(),
frame_buf: Box::new([0; GB_WIDTH * GB_HEIGHT * 4]),
int: Default::default(),
ctrl: Default::default(),
@@ -489,7 +491,7 @@ impl Default for Ppu {
}
}
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Default)]
pub(crate) struct Interrupt {
_vblank: bool,
_lcd_stat: bool,
@@ -513,7 +515,7 @@ impl Interrupt {
}
}
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Default)]
pub(crate) struct ScreenPosition {
/// 0xFF42 | SCY - Scroll Y
pub(crate) scroll_y: u8,
@@ -529,7 +531,7 @@ pub(crate) struct ScreenPosition {
pub(crate) window_x: u8,
}
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Default)]
pub(crate) struct Monochrome {
/// 0xFF47 | BGP - Background Palette Data
pub(crate) bg_palette: BackgroundPalette,
@@ -539,7 +541,7 @@ pub(crate) struct Monochrome {
pub(crate) obj_palette_1: ObjectPalette,
}
#[derive(Debug, Clone)]
#[derive(Debug)]
pub(crate) struct ObjectAttributeTable {
buf: Box<[u8; OAM_SIZE]>,
}
@@ -606,75 +608,42 @@ impl<'a> From<&'a [u8; 4]> for ObjectAttribute {
}
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug)]
struct ObjectBuffer {
buf: [Option<ObjectAttribute>; OBJECT_LIMIT],
inner: [Option<ObjectAttribute>; OBJECT_LIMIT],
len: usize,
}
impl ObjectBuffer {
fn iter(&self) -> std::slice::Iter<'_, Option<ObjectAttribute>> {
self.into_iter()
}
}
impl<'a> IntoIterator for &'a ObjectBuffer {
type Item = &'a Option<ObjectAttribute>;
type IntoIter = std::slice::Iter<'a, Option<ObjectAttribute>>;
fn into_iter(self) -> Self::IntoIter {
self.buf.iter()
}
}
impl<'a> IntoIterator for &'a mut ObjectBuffer {
type Item = &'a Option<ObjectAttribute>;
type IntoIter = std::slice::Iter<'a, Option<ObjectAttribute>>;
fn into_iter(self) -> Self::IntoIter {
self.buf.iter()
}
}
impl ObjectBuffer {
fn is_full(&self) -> bool {
self.len == OBJECT_LIMIT
}
fn clear(&mut self) {
self.buf = [Default::default(); 10];
self.inner = [Default::default(); 10];
self.len = 0;
}
fn add(&mut self, attr: ObjectAttribute) {
self.buf[self.len] = Some(attr);
self.inner[self.len] = Some(attr);
self.len += 1;
}
fn remove(&mut self, attr: &ObjectAttribute) {
let maybe_index = self.buf.iter().position(|maybe_attr| match maybe_attr {
Some(other_attr) => attr == other_attr,
None => false,
});
if let Some(i) = maybe_index {
self.buf[i] = None;
}
fn iter_mut(&mut self) -> std::slice::IterMut<'_, Option<ObjectAttribute>> {
self.inner.iter_mut()
}
}
impl Default for ObjectBuffer {
fn default() -> Self {
Self {
buf: [Default::default(); OBJECT_LIMIT],
inner: [Default::default(); OBJECT_LIMIT],
len: Default::default(),
}
}
}
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Default)]
struct PixelFetcher {
x_pos: u8,
back: BackgroundFetcher,
@@ -793,7 +762,7 @@ trait Fetcher {
fn hblank_reset(&mut self);
}
#[derive(Debug, Clone, Copy)]
#[derive(Debug)]
struct BackgroundFetcher {
state: FetcherState,
tile: TileBuilder,
@@ -859,7 +828,7 @@ impl Default for BackgroundFetcher {
}
}
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Default)]
struct ObjectFetcher {
state: FetcherState,
tile: TileBuilder,
@@ -881,7 +850,7 @@ impl Fetcher for ObjectFetcher {
}
}
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Default)]
struct WindowLineCounter {
count: u8,
}
@@ -903,13 +872,13 @@ impl WindowLineCounter {
#[derive(Debug, Clone, Copy)]
enum FetcherState {
TileNumber,
ToLowByteSleep,
TileLowByte,
ToHighByteSleep,
TileHighByte,
ToFifoSleep,
SendToFifoOne,
SendToFifoTwo,
SleepOne,
TileLow,
SleepTwo,
TileHigh,
SleepThree,
ToFifoOne,
ToFifoTwo,
}
impl Default for FetcherState {
@@ -918,12 +887,12 @@ impl Default for FetcherState {
}
}
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Default)]
struct BgPixelProperty {
shade_id: u8,
}
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Default)]
struct ObjPixelProperty {
shade_id: u8,
palette_kind: ObjectPaletteKind,
@@ -932,7 +901,7 @@ struct ObjPixelProperty {
// FIXME: Fifo Registers have a known size. Are heap allocations
// really necessary here?
#[derive(Debug, Clone)]
#[derive(Debug)]
struct PixelFifo {
back: VecDeque<BgPixelProperty>,
obj: VecDeque<ObjPixelProperty>,
@@ -963,7 +932,7 @@ impl Default for PixelFifo {
}
}
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Default)]
struct TileBuilder {
id: Option<u8>,
low: Option<u8>,
@@ -988,7 +957,7 @@ impl TileBuilder {
}
}
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Default)]
struct OamScanState {
count: u8,
mode: OamScanMode,
@@ -1009,8 +978,8 @@ impl OamScanState {
self.count
}
fn mode(&self) -> OamScanMode {
self.mode
fn mode(&self) -> &OamScanMode {
&self.mode
}
fn next(&mut self) {
@@ -1035,7 +1004,7 @@ impl Default for OamScanMode {
}
}
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Default)]
struct WindowStatus {
/// This will be true if WY == LY at any point in the frame thus far
coincidence: bool,

View File

@@ -1,6 +1,6 @@
use crate::instruction::Cycle;
use crate::Cycle;
#[derive(Debug, Default, Clone)]
#[derive(Debug, Default)]
pub(crate) struct DirectMemoryAccess {
pub(crate) state: DmaState,
cycle: Cycle,
@@ -9,7 +9,7 @@ pub(crate) struct DirectMemoryAccess {
}
impl DirectMemoryAccess {
pub(crate) fn clock(&mut self) -> Option<(u16, u16)> {
pub(crate) fn tick(&mut self) -> Option<(u16, u16)> {
match self.state {
DmaState::Pending => {
self.cycle += 1;
@@ -28,7 +28,7 @@ impl DirectMemoryAccess {
let src_addr = self
.start
.addr
.0
.as_mut()
.expect("Source Address present during DMA Transfer");
@@ -56,9 +56,9 @@ impl DirectMemoryAccess {
}
fn reset(&mut self) {
self.cycle = Cycle::new(0);
self.cycle = 0;
self.state = DmaState::Disabled;
self.start.addr = None;
self.start.0 = None;
}
}
@@ -75,26 +75,21 @@ impl Default for DmaState {
}
}
#[derive(Debug, Default, Clone, Copy)]
pub(crate) struct DmaAddress {
/// The current *source* address of the DMA Transfer
///
/// NB: Will be None if no DMA Transfer is in progress
addr: Option<u16>,
}
#[derive(Debug, Clone, Copy, Default)]
pub(crate) struct DmaAddress(Option<u16>);
impl DmaAddress {
pub(crate) fn update(&mut self, byte: u8, state: &mut DmaState) {
let start = (byte as u16) << 8;
self.addr = Some(start);
self.0 = Some(start);
*state = DmaState::Pending;
}
}
impl From<DmaAddress> for u8 {
fn from(ctrl: DmaAddress) -> Self {
match ctrl.addr {
match ctrl.0 {
Some(addr) => (addr >> 8) as u8,
None => 0xFF, // TODO: What garbage value should be here?
}

79
src/scheduler.rs Normal file
View File

@@ -0,0 +1,79 @@
use crate::Cycle;
use std::collections::BinaryHeap;
#[derive(Debug)]
pub(crate) struct Scheduler {
timestamp: Cycle,
queue: BinaryHeap<Event>,
}
impl Scheduler {
pub(crate) fn init() -> Self {
let mut scheduler = Self {
timestamp: Default::default(),
queue: Default::default(),
};
scheduler.push(Event {
kind: EventKind::TimestampOverflow,
timestamp: Cycle::MAX,
cb: |_delay| panic!("Reached Cycle::MAX"),
});
scheduler
}
pub(crate) fn push(&mut self, event: Event) {
self.queue.push(event);
}
pub(crate) fn step(&mut self, cycles: Cycle) {
self.timestamp += cycles;
loop {
let should_pop = match self.queue.peek() {
Some(event) => self.timestamp >= event.timestamp,
None => false,
};
if !should_pop {
break;
}
let event = self.queue.pop().expect("Pop Event from Scheduler Queue");
(event.cb)(self.timestamp - event.timestamp);
}
}
}
#[derive(Debug)]
pub(crate) struct Event {
kind: EventKind,
cb: fn(Cycle),
pub(crate) timestamp: Cycle,
}
impl Eq for Event {}
impl PartialEq for Event {
fn eq(&self, other: &Self) -> bool {
self.kind == other.kind && self.timestamp == other.timestamp
}
}
impl PartialOrd for Event {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Event {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.timestamp.cmp(&other.timestamp)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum EventKind {
TimestampOverflow,
}

View File

@@ -1,6 +1,6 @@
use bitfield::bitfield;
#[derive(Debug, Clone, Copy, Default)]
#[derive(Debug, Default)]
pub(crate) struct Serial {
/// 0xFF01 | SB - Serial Transfer Data
pub(crate) next: u8,

View File

@@ -1,23 +1,37 @@
use bitfield::bitfield;
#[derive(Debug, Clone, Copy)]
#[derive(Debug)]
pub(crate) struct Timer {
/// 0xFF07 | TAC - Timer Control
pub(crate) ctrl: TimerControl,
/// 0xFF05 | TIMA - Timer Counter
pub(crate) counter: u8,
counter: u8,
/// 0xFF06 | TMA - Timer Modulo
pub(crate) modulo: u8,
/// 0xFF04 | DIV - Divider Register
pub(crate) divider: u16,
prev_and_result: Option<u8>,
and_result: Option<u8>,
interrupt: bool,
state: State,
}
impl Timer {
pub(crate) fn clock(&mut self) {
pub(crate) fn tick(&mut self) {
use State::*;
use TimerSpeed::*;
match self.state {
TIMAOverflow(_) | AbortedTIMAOverflow(_) => self.next(),
LoadTMA => {
self.counter = self.modulo;
self.interrupt = true;
self.next();
}
Normal => {}
}
self.divider = self.divider.wrapping_add(1);
// Get Bit Position
@@ -29,27 +43,34 @@ impl Timer {
};
let bit = (self.divider >> bit) as u8 & 0x01;
let timer_enable = self.ctrl.enabled() as u8;
let and_result = bit & timer_enable;
let new_result = bit & self.ctrl.enabled() as u8;
if let Some(0x01) = self.prev_and_result {
if and_result == 0x00 {
if let Some(0x01) = self.and_result {
if new_result == 0x00 {
// Falling Edge, increase TIMA Register
self.increment_tima();
self.inc_counter();
}
}
self.prev_and_result = Some(and_result);
self.and_result = Some(new_result);
}
fn increment_tima(&mut self) {
let (result, did_overflow) = self.counter.overflowing_add(1);
/// 0xFF05 | TIMA - Timer Counter
pub(crate) fn tima(&self) -> u8 {
self.counter
}
self.counter = if did_overflow {
self.interrupt = true;
self.modulo
} else {
result
/// 0xFF05 | TIMA - Timer Counter
pub(crate) fn set_tima(&mut self, byte: u8) {
use State::*;
match self.state {
Normal | AbortedTIMAOverflow(_) => self.counter = byte,
TIMAOverflow(step) => {
self.counter = byte;
self.state = AbortedTIMAOverflow(step);
}
LoadTMA => {}
}
}
@@ -60,6 +81,27 @@ impl Timer {
pub(crate) fn set_interrupt(&mut self, value: bool) {
self.interrupt = value;
}
fn inc_counter(&mut self) {
let (sum, did_overflow) = self.counter.overflowing_add(1);
self.counter = if did_overflow { 0 } else { sum };
if did_overflow {
self.state = State::TIMAOverflow(0);
}
}
fn next(&mut self) {
use State::*;
self.state = match self.state {
Normal | LoadTMA => Normal,
AbortedTIMAOverflow(4) => Normal,
TIMAOverflow(4) => LoadTMA,
AbortedTIMAOverflow(step) => AbortedTIMAOverflow(step + 1),
TIMAOverflow(step) => TIMAOverflow(step + 1),
}
}
}
impl Default for Timer {
@@ -70,7 +112,8 @@ impl Default for Timer {
modulo: 0,
divider: 0,
interrupt: false,
prev_and_result: None,
and_result: None,
state: State::Normal,
}
}
}
@@ -132,3 +175,11 @@ impl From<TimerControl> for u8 {
ctrl.0
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum State {
TIMAOverflow(u8),
AbortedTIMAOverflow(u8),
Normal,
LoadTMA,
}

View File

@@ -5,7 +5,7 @@ const VARIABLE_WORK_RAM_SIZE: usize = WORK_RAM_SIZE;
const WORK_RAM_START_ADDRESS: usize = 0xC000;
const VARIABLE_WORK_RAM_START_ADDRESS: usize = 0xD000;
#[derive(Debug, Clone)]
#[derive(Debug)]
pub(crate) struct WorkRam {
bank: Box<[u8; WORK_RAM_SIZE]>,
}
@@ -28,48 +28,25 @@ impl Default for WorkRam {
}
}
#[derive(Debug, Clone, Copy)]
enum BankNumber {
One = 1,
Two = 2,
Three = 3,
Four = 4,
Five = 5,
Six = 6,
Seven = 7,
}
#[derive(Debug, Clone)]
#[derive(Debug)]
pub(crate) struct VariableWorkRam {
current: BankNumber,
bank_n: Box<[[u8; VARIABLE_WORK_RAM_SIZE]; 7]>, // 4K for Variable amount of Banks (Banks 1 -> 7) in Game Boy Colour
buf: Box<[u8; VARIABLE_WORK_RAM_SIZE]>, // 4K for Variable amount of Banks (Banks 1 -> 7) in Game Boy Colour
}
impl Default for VariableWorkRam {
fn default() -> Self {
Self {
current: BankNumber::One,
bank_n: Box::new([[0u8; VARIABLE_WORK_RAM_SIZE]; 7]),
buf: Box::new([0u8; VARIABLE_WORK_RAM_SIZE]),
}
}
}
impl VariableWorkRam {
fn set_current_bank(&mut self, bank: BankNumber) {
self.current = bank;
}
fn get_current_bank(&self) -> BankNumber {
self.current
}
}
impl BusIo for VariableWorkRam {
fn write_byte(&mut self, addr: u16, byte: u8) {
self.bank_n[self.current as usize][addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] = byte;
self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS] = byte;
}
fn read_byte(&self, addr: u16) -> u8 {
self.bank_n[self.current as usize][addr as usize - VARIABLE_WORK_RAM_START_ADDRESS]
self.buf[addr as usize - VARIABLE_WORK_RAM_START_ADDRESS]
}
}