diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml new file mode 100644 index 0000000..108b877 --- /dev/null +++ b/.github/workflows/rust.yml @@ -0,0 +1,34 @@ +name: Rust + +on: [push] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v1 + - name: Prepare + run: sudo apt install libasound2-dev libxcursor-dev + - name: Build (stable) + run: cargo build --verbose + - name: Build examples (stable) + run: cargo build --verbose --examples + - name: Test (stable) + run: cargo test --verbose + - name: Install nightly + uses: actions-rs/toolchain@v1 + with: + toolchain: nightly + override: true + - name: Build (nightly) + run: | + cd core + cargo build --verbose --features readme + - name: Build examples (nightly) + run: | + cd core + cargo build --verbose --examples --features readme + - name: Test (nightly) + run: | + cd core + cargo test --verbose --features readme diff --git a/README.md b/README.md index 6afafc1..ee48f17 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ -# rGB +# rgy -Try emulating Game Boy in Rust +No-std cross-platform Rust GameBoy emulator library. Rust GameboY (RGY, or Real GaY). + +[![Latest version](https://img.shields.io/crates/v/rgy.svg)](https://crates.io/crates/rgy) +[![Documentation](https://docs.rs/rgy/badge.svg)](https://docs.rs/rgy) +[![License](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT) +[![Actions Status](https://github.com/YushiOMOTE/rgy/workflows/Rust/badge.svg)](https://github.com/YushiOMOTE/rgy/actions) ![demo](https://raw.github.com/wiki/YushiOMOTE/gbr/media/demo.gif) ![screens](https://raw.github.com/wiki/YushiOMOTE/gbr/media/demo_screens.jpg) diff --git a/codegen/templates/root.rs b/codegen/templates/root.rs index 61e583f..591edfa 100644 --- a/codegen/templates/root.rs +++ b/codegen/templates/root.rs @@ -201,10 +201,12 @@ fn op_{{i.code | hex}}(arg: u16, cpu: &mut Cpu, mmu: &mut Mmu) -> (usize, usize) } {% endfor %} +/// Return the mnemonic string for the given opcode. pub fn mnem(code: u16) -> &'static str { MNEMONICS.get(&code).unwrap_or(&"(unknown opcode)") } +/// Decodes the opecode and actually executes one instruction. pub fn decode(code: u16, arg: u16, cpu: &mut Cpu, mmu: &mut Mmu) -> (usize, usize) { trace!("{:04x}: {:04x}: {}", cpu.get_pc(), code, mnem(code)); diff --git a/core/Cargo.toml b/core/Cargo.toml index 85d960b..4ee4b15 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -3,6 +3,13 @@ name = "rgy" version = "0.1.0" authors = ["Yushi Omote "] edition = "2018" +keywords = ["gameboy", "emulator"] +description = "No-std Rust GameBoy emulator library" +homepage = "https://github.com/yushiomote/rgy" +repository = "https://github.com/yushiomote/rgy" +documentation = "https://docs.rs/rgy" +license = "MIT" +readme = "README.md" [dependencies] lazy_static = { version = "1.2", features = ["spin_no_std"] } @@ -22,3 +29,4 @@ core_affinity = "0.5" [features] default = [] color = [] +readme = [] diff --git a/core/examples/empty.rs b/core/examples/empty.rs new file mode 100644 index 0000000..5220f82 --- /dev/null +++ b/core/examples/empty.rs @@ -0,0 +1,81 @@ +use rgy::{Config, Key, Stream, VRAM_HEIGHT, VRAM_WIDTH}; + +struct Hardware { + display: Vec>, +} + +impl Hardware { + fn new() -> Self { + // Create a frame buffer with the size VRAM_WIDTH * VRAM_HEIGHT. + let display = vec![vec![0u32; VRAM_HEIGHT]; VRAM_WIDTH]; + + Self { display } + } +} + +impl rgy::Hardware for Hardware { + fn vram_update(&mut self, line: usize, buffer: &[u32]) { + // `line` corresponds to the y coordinate. + let y = line; + + for (x, col) in buffer.iter().enumerate() { + self.display[x][y] = *col; + } + } + + fn joypad_pressed(&mut self, key: Key) -> bool { + // Read a keyboard device and check if the `key` is pressed or not. + println!("Check if {:?} is pressed", key); + false + } + + fn sound_play(&mut self, _stream: Box) { + // Play the wave provided `Stream`. + } + + fn clock(&mut self) -> u64 { + // Return the epoch in microseconds. + let epoch = std::time::SystemTime::now() + .duration_since(std::time::UNIX_EPOCH) + .expect("Couldn't get epoch"); + epoch.as_micros() as u64 + } + + fn send_byte(&mut self, _b: u8) { + // Send a byte to a serial port. + } + + fn recv_byte(&mut self) -> Option { + // Try to read a byte from a serial port. + None + } + + fn sched(&mut self) -> bool { + // `true` to continue, `false` to stop the emulator. + println!("It's running!"); + true + } + + fn load_ram(&mut self, size: usize) -> Vec { + // Return save data. + vec![0; size] + } + + fn save_ram(&mut self, _ram: &[u8]) { + // Store save data. + } +} + +fn main() { + // Create the default config. + let cfg = Config::new(); + + // Create the hardware instance. + let hw = Hardware::new(); + + // The content of a ROM file, which can be downloaded from the Internet. + let rom = vec![0u8; 1024]; + + // Run the emulator. + rgy::run(cfg, &rom, hw); +} diff --git a/core/examples/pc/hardware.rs b/core/examples/pc/hardware.rs index 9243e1c..52a7358 100644 --- a/core/examples/pc/hardware.rs +++ b/core/examples/pc/hardware.rs @@ -11,7 +11,7 @@ use std::sync::{ }; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use rgy::hardware::{self, Key, Stream, VRAM_HEIGHT, VRAM_WIDTH}; +use rgy::{Key, Stream, VRAM_HEIGHT, VRAM_WIDTH}; #[derive(Clone)] pub struct Hardware { @@ -153,7 +153,7 @@ impl Hardware { } } -impl hardware::Hardware for Hardware { +impl rgy::Hardware for Hardware { fn vram_update(&mut self, line: usize, buf: &[u32]) { let mut vram = self.vram.lock().unwrap(); for i in 0..buf.len() { diff --git a/core/examples/pc/main.rs b/core/examples/pc/main.rs index 20c916a..53466d4 100644 --- a/core/examples/pc/main.rs +++ b/core/examples/pc/main.rs @@ -77,9 +77,9 @@ fn main() { set_affinity(); if opt.debug { - rgy::run_debug(to_cfg(opt), rom, hw1, Debugger::new()); + rgy::run_debug(to_cfg(opt), &rom, hw1, Debugger::new()); } else { - rgy::run(to_cfg(opt), rom, hw1); + rgy::run(to_cfg(opt), &rom, hw1); } }); diff --git a/core/src/cgb.rs b/core/src/cgb.rs index 9f0edd7..6b376f9 100644 --- a/core/src/cgb.rs +++ b/core/src/cgb.rs @@ -12,6 +12,7 @@ pub struct Cgb { wram_bank: Vec>, } +#[allow(unused)] impl Cgb { pub fn new() -> Self { Self { diff --git a/core/src/cpu.rs b/core/src/cpu.rs index ba04fc4..23bd0b0 100644 --- a/core/src/cpu.rs +++ b/core/src/cpu.rs @@ -6,6 +6,7 @@ use log::*; use alloc::fmt; +/// Represents CPU state. #[derive(Clone)] pub struct Cpu { a: u8, @@ -53,6 +54,7 @@ impl fmt::Display for Cpu { } impl Cpu { + /// Create a new CPU state. pub fn new() -> Cpu { Cpu { a: 0, @@ -70,11 +72,18 @@ impl Cpu { } } + /// Switch the CPU state to halting. pub fn halt(&mut self) { debug!("Halted"); - // self.halt = true; + // TODO: self.halt = true; } + /// Execute a single instruction. + /// + /// The function fetches an instruction code from the memory, + /// decodes it, and updates the CPU/memory state accordingly. + /// The return value is the number of clock cycles consumed by the instruction. + /// If the CPU is in the halt state, the function does nothing but returns a fixed clock cycle. pub fn execute(&mut self, mmu: &mut Mmu) -> usize { if self.halt { 4 @@ -86,16 +95,20 @@ impl Cpu { } } + /// Disable interrupts to this CPU. pub fn disable_interrupt(&mut self) { debug!("Disable interrupt"); self.ime = false; } + /// Enable interrupts to this CPU. pub fn enable_interrupt(&mut self) { debug!("Enable interrupt"); self.ime = true; } + /// Check if pending interrupts in the interrupt controller, + /// and process them if any. pub fn check_interrupt(&mut self, mmu: &mut Mmu, ic: &Device) -> usize { if !self.ime { if self.halt { @@ -131,24 +144,32 @@ impl Cpu { self.set_pc(value as u16); } - pub fn stop(&self) {} + /// Stop the CPU. + pub fn stop(&self) { + // TODO: Stop. + } + /// Gets the value of `z` flag in the flag register. pub fn get_zf(&self) -> bool { self.f & 0x80 == 0x80 } + /// Gets the value of `n` flag in the flag register. pub fn get_nf(&self) -> bool { self.f & 0x40 == 0x40 } + /// Gets the value of `h` flag in the flag register. pub fn get_hf(&self) -> bool { self.f & 0x20 == 0x20 } + /// Gets the value of `c` flag in the flag register. pub fn get_cf(&self) -> bool { self.f & 0x10 == 0x10 } + /// Updates the value of `z` flag in the flag register. pub fn set_zf(&mut self, v: bool) { if v { self.f = self.f | 0x80 @@ -157,6 +178,7 @@ impl Cpu { } } + /// Updates the value of `n` flag in the flag register. pub fn set_nf(&mut self, v: bool) { if v { self.f = self.f | 0x40 @@ -165,6 +187,7 @@ impl Cpu { } } + /// Updates the value of `h` flag in the flag register. pub fn set_hf(&mut self, v: bool) { if v { self.f = self.f | 0x20 @@ -173,6 +196,7 @@ impl Cpu { } } + /// Updates the value of `c` flag in the flag register. pub fn set_cf(&mut self, v: bool) { if v { self.f = self.f | 0x10 @@ -181,126 +205,155 @@ impl Cpu { } } + /// Updates the value of `a` register. pub fn set_a(&mut self, v: u8) { self.a = v } + /// Updates the value of `b` register. pub fn set_b(&mut self, v: u8) { self.b = v } + /// Updates the value of `c` register. pub fn set_c(&mut self, v: u8) { self.c = v } + /// Updates the value of `d` register. pub fn set_d(&mut self, v: u8) { self.d = v } + /// Updates the value of `e` register. pub fn set_e(&mut self, v: u8) { self.e = v } + /// Updates the value of `h` register. pub fn set_h(&mut self, v: u8) { self.h = v } + /// Updates the value of `l` register. pub fn set_l(&mut self, v: u8) { self.l = v } + /// Updates the value of `a` and `f` register as a single 16-bit register. pub fn set_af(&mut self, v: u16) { self.a = (v >> 8) as u8; self.f = (v & 0xf0) as u8; } + /// Updates the value of `b` and `c` register as a single 16-bit register. pub fn set_bc(&mut self, v: u16) { self.b = (v >> 8) as u8; self.c = v as u8; } + /// Updates the value of `d` and `e` register as a single 16-bit register pub fn set_de(&mut self, v: u16) { self.d = (v >> 8) as u8; self.e = v as u8; } + /// Updates the value of `h` and `l` register as a single 16-bit register. pub fn set_hl(&mut self, v: u16) { self.h = (v >> 8) as u8; self.l = v as u8; } + /// Gets the value of `a` register. pub fn get_a(&self) -> u8 { self.a } + /// Gets the value of `b` register. pub fn get_b(&self) -> u8 { self.b } + /// Gets the value of `c` register. pub fn get_c(&self) -> u8 { self.c } + /// Gets the value of `d` register. pub fn get_d(&self) -> u8 { self.d } + /// Gets the value of `e` register. pub fn get_e(&self) -> u8 { self.e } + /// Gets the value of `h` register. pub fn get_h(&self) -> u8 { self.h } + /// Gets the value of `l` register. pub fn get_l(&self) -> u8 { self.l } + /// Gets the value of `a` and `f` register as a single 16-bit register. pub fn get_af(&self) -> u16 { (self.a as u16) << 8 | self.f as u16 } + /// Gets the value of `b` and `c` register as a single 16-bit register. pub fn get_bc(&self) -> u16 { (self.b as u16) << 8 | self.c as u16 } + /// Gets the value of `d` and `e` register as a single 16-bit register. pub fn get_de(&self) -> u16 { (self.d as u16) << 8 | self.e as u16 } + /// Gets the value of `h` and `l` register as a single 16-bit register. pub fn get_hl(&self) -> u16 { (self.h as u16) << 8 | self.l as u16 } + /// Gets the value of the program counter. pub fn get_pc(&self) -> u16 { self.pc } + /// Updates the value of the program counter. pub fn set_pc(&mut self, v: u16) { self.pc = v } + /// Gets the value of the stack pointer register. pub fn get_sp(&self) -> u16 { self.sp } + /// Updates the value of the stack pointer register. pub fn set_sp(&mut self, v: u16) { self.sp = v } + /// Pushes a 16-bit value to the stack, updating the stack pointer register. pub fn push(&mut self, mmu: &mut Mmu, v: u16) { let p = self.get_sp().wrapping_sub(2); self.set_sp(self.get_sp().wrapping_sub(2)); mmu.set16(p, v) } + /// Pops a 16-bit value from the stack, updating the stack pointer register. pub fn pop(&mut self, mmu: &mut Mmu) -> u16 { let p = self.get_sp(); self.set_sp(self.get_sp().wrapping_add(2)); mmu.get16(p) } + /// Fetches an opcode from the memory and returns it with its length. pub fn fetch(&self, mmu: &Mmu) -> (u16, u16) { let pc = self.get_pc(); @@ -319,6 +372,7 @@ impl Cpu { mod test { use super::*; use crate::inst::decode; + use alloc::{vec, vec::Vec}; fn write(mmu: &mut Mmu, m: Vec) { for i in 0..m.len() { diff --git a/core/src/debug.rs b/core/src/debug.rs index eaf8f42..4b8562a 100644 --- a/core/src/debug.rs +++ b/core/src/debug.rs @@ -2,19 +2,31 @@ use crate::cpu::Cpu; use crate::device::IoHandler; use crate::mmu::{MemRead, MemWrite, Mmu}; +/// Debugger interface. +/// +/// The users of this library can implement this interface to inspect the state of the emulator. pub trait Debugger: IoHandler { + /// The function is called on the initialization phase. fn init(&mut self, mmu: &Mmu); + + /// The function is called right before the emulator starts executing an instruction. Deprecated. fn take_cpu_snapshot(&mut self, cpu: Cpu); + + /// Decode an instruction. fn on_decode(&mut self, mmu: &Mmu); + + /// Check if the external signal is triggered. Deprecated. fn check_signal(&mut self); } impl dyn Debugger { + /// Create an empty debugger. pub fn empty() -> NullDebugger { NullDebugger } } +/// Empty debugger which does nothing. pub struct NullDebugger; impl Debugger for NullDebugger { diff --git a/core/src/device.rs b/core/src/device.rs index 7582b4e..71bfcb0 100644 --- a/core/src/device.rs +++ b/core/src/device.rs @@ -3,13 +3,16 @@ use core::cell::{Ref, RefCell, RefMut}; use crate::mmu::{MemHandler, MemRead, MemWrite, Mmu}; +/// The wrapper type for I/O handlers to register to MMU. pub struct Device(Rc>, bool); impl Device { + /// Create a new device. pub fn new(inner: T) -> Self { Self::inner(inner, false) } + /// Create a new mediater device. pub fn mediate(inner: T) -> Self { Self::inner(inner, true) } @@ -18,27 +21,34 @@ impl Device { Self(Rc::new(RefCell::new(inner)), debug) } + /// Immutably borrow the underlying I/O handler. pub fn borrow<'a>(&'a self) -> Ref<'a, T> { self.0.borrow() } + /// Mutabully borrow the underlying I/O handler. pub fn borrow_mut<'a>(&'a self) -> RefMut<'a, T> { self.0.borrow_mut() } } impl Device { + /// Return the memory-mapped I/O handler of the device. pub fn handler(&self) -> IoMemHandler { IoMemHandler(self.0.clone(), self.1) } } +/// The trait which allows to hook I/O access from the CPU. pub trait IoHandler { + /// The function is called when the CPU attempts to read the memory-mapped I/O. fn on_read(&mut self, mmu: &Mmu, addr: u16) -> MemRead; + /// The function is called when the CPU attempts to write the memory-mapped I/O. fn on_write(&mut self, mmu: &Mmu, addr: u16, value: u8) -> MemWrite; } +/// The handler to intercept memory-mapped I/O. pub struct IoMemHandler(Rc>, bool); impl MemHandler for IoMemHandler { diff --git a/core/src/gpu.rs b/core/src/gpu.rs index e1f560e..a988ed8 100644 --- a/core/src/gpu.rs +++ b/core/src/gpu.rs @@ -93,6 +93,7 @@ fn from_palette(p: Vec) -> u8 { u8::from(p[0]) | u8::from(p[1]) << 2 | u8::from(p[2]) << 4 | u8::from(p[3]) << 6 } +#[allow(unused)] struct SpriteAttribute<'a> { ypos: u16, xpos: u16, diff --git a/core/src/hardware.rs b/core/src/hardware.rs index 84e046a..163161e 100644 --- a/core/src/hardware.rs +++ b/core/src/hardware.rs @@ -3,25 +3,40 @@ use alloc::rc::Rc; use alloc::vec::Vec; use core::cell::RefCell; +/// The width of the VRAM. pub const VRAM_WIDTH: usize = 160; + +/// The height of the VRAM. pub const VRAM_HEIGHT: usize = 144; +/// Represents a key of the joypad. #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub enum Key { + /// Cursor right key. Right, + /// Cursor left key. Left, + /// Cursor up key. Up, + /// Cursor down key. Down, + /// A key. A, + /// B key. B, + /// Select key. Select, + /// Start key. Start, } +/// Sound wave stream which generates the wave to be played by the sound device. pub trait Stream: Send + 'static { + /// The maximum value of the amplitude returned by this stream. fn max(&self) -> u16; - // Return value is in range 0 - 16 + /// The argument takes the sample rate, and the return value indicates the amplitude, + /// whose max value is determined by [`Stream::max`][]. fn next(&mut self, rate: u32) -> u16; } @@ -38,25 +53,40 @@ impl HardwareHandle { } } +/// The interface to abstracts the OS-specific functions. +/// +/// The users of this emulator library need to implement this trait, +/// providing OS-specific functions. pub trait Hardware { + /// Called when one horizontal line in the display is updated. fn vram_update(&mut self, line: usize, buffer: &[u32]); + /// Called when the emulator checks if the key is pressed. fn joypad_pressed(&mut self, key: Key) -> bool; + /// Called when the emulator plays a sound. + /// The stream in the argument is the stream which keeps returning wave patterns. fn sound_play(&mut self, stream: Box); - /// Epoch in microseconds + /// Clock source used by the emulator. + /// The return value needs to be epoch time in microseconds. fn clock(&mut self) -> u64; + /// Send one byte to the serial port. fn send_byte(&mut self, b: u8); + /// Try receiving one byte from the serial port. fn recv_byte(&mut self) -> Option; + /// Called every time the CPU executes one instruction. + /// Returning `false` stops the emulator. fn sched(&mut self) -> bool { true } + /// Called when the CPU attempts to write save data to the cartridge battery-backed RAM. fn load_ram(&mut self, size: usize) -> Vec; + /// Called when the CPU attempts to read save data from the cartridge battery-backed RAM. fn save_ram(&mut self, ram: &[u8]); } diff --git a/core/src/inst.rs b/core/src/inst.rs index f282b2e..b13aa8e 100644 --- a/core/src/inst.rs +++ b/core/src/inst.rs @@ -6469,10 +6469,12 @@ fn op_cbff(arg: u16, cpu: &mut Cpu, mmu: &mut Mmu) -> (usize, usize) { (8, 2) } +/// Return the mnemonic string for the given opcode. pub fn mnem(code: u16) -> &'static str { MNEMONICS.get(&code).unwrap_or(&"(unknown opcode)") } +/// Decodes the opecode and actually executes one instruction. pub fn decode(code: u16, arg: u16, cpu: &mut Cpu, mmu: &mut Mmu) -> (usize, usize) { trace!("{:04x}: {:04x}: {}", cpu.get_pc(), code, mnem(code)); diff --git a/core/src/lib.rs b/core/src/lib.rs index 7a504ab..f2e2b16 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -1,27 +1,147 @@ +//! +//! `rgy` is no-std cross-platform Rust GameBoy emulator library. +//! +//! The users of this library only needs to implement [`Hardware`][] trait, which abstracts OS-specific function. +//! Once it's implemented, the emulator works. +//! +//! The following code is the example which just implements `Hardware`. The implementation does nothing. +//! You can replace the body of each function with the actual meaningful logic. +//! +//! ```rust,no_run +//! use rgy::{Config, Key, Stream, VRAM_HEIGHT, VRAM_WIDTH}; +//! +//! struct Hardware { +//! dummy_display: Vec>, +//! } +//! +//! impl Hardware { +//! fn new() -> Self { +//! // Create a frame buffer with the size VRAM_WIDTH * VRAM_HEIGHT. +//! let dummy_display = vec![vec![0u32; VRAM_HEIGHT]; VRAM_WIDTH]; +//! +//! Self { dummy_display } +//! } +//! } +//! +//! impl rgy::Hardware for Hardware { +//! // Called when a horizontal line in the display is updated by the emulator. +//! fn vram_update(&mut self, line: usize, buffer: &[u32]) { +//! // `line` corresponds to the y coordinate. +//! let y = line; +//! +//! for (x, col) in buffer.iter().enumerate() { +//! // TODO: Update the pixels in the actual display here. +//! self.dummy_display[x][y] = *col; +//! } +//! } +//! +//! // Called when the emulator checks if a key is pressed or not. +//! fn joypad_pressed(&mut self, key: Key) -> bool { +//! println!("Is {:?} pressed?", key); +//! +//! // TODO: Read a keyboard device and check if the `key` is pressed or not. +//! +//! false +//! } +//! +//! // Called when the emulator plays a sound. +//! fn sound_play(&mut self, _stream: Box) { +//! // TODO: Play the wave pattern provided `Stream`. +//! } +//! +//! // Provides clock for the emulator. +//! fn clock(&mut self) -> u64 { +//! // TODO: Return the epoch in microseconds. +//! let epoch = std::time::SystemTime::now() +//! .duration_since(std::time::UNIX_EPOCH) +//! .expect("Couldn't get epoch"); +//! epoch.as_micros() as u64 +//! } +//! +//! // Called when the emulator sends a byte to the serial port. +//! fn send_byte(&mut self, _b: u8) { +//! // TODO: Send a byte to a serial port. +//! } +//! +//! // Called when the emulator peeks a byte from the serial port. +//! fn recv_byte(&mut self) -> Option { +//! // TODO: Check the status of the serial port and read a byte if any. +//! None +//! } +//! +//! // Called every time the emulator executes an instruction. +//! fn sched(&mut self) -> bool { +//! // TODO: Do some periodic jobs if any. Return `true` to continue, `false` to stop the emulator. +//! println!("It's running!"); +//! true +//! } +//! +//! // Called when the emulator stores the save data to the battery-backed RAM. +//! fn load_ram(&mut self, size: usize) -> Vec { +//! // TODO: Return save data. +//! vec![0; size] +//! } +//! +//! // Called when the emulator loads the save data from the battery-backed RAM. +//! fn save_ram(&mut self, _ram: &[u8]) { +//! // TODO: Store save data. +//! } +//! } +//! +//! fn main() { +//! // Create the default config. +//! let cfg = Config::new(); +//! +//! // Create the hardware instance. +//! let hw = Hardware::new(); +//! +//! // TODO: The content of a ROM file, which can be downloaded from the Internet. +//! let rom = vec![0u8; 1024]; +//! +//! // Run the emulator. +//! rgy::run(cfg, &rom, hw); +//! } +//! ``` + #![no_std] +#![cfg_attr(feature = "readme", feature(external_doc))] +#![warn(missing_docs)] + +#[cfg_attr(feature = "readme", doc(include = "../../README.md"))] +type _Doctest = (); extern crate alloc; mod alu; mod cgb; -pub mod cpu; -pub mod debug; -pub mod device; mod dma; mod fc; mod gpu; -pub mod hardware; mod ic; -pub mod inst; mod joypad; mod mbc; -pub mod mmu; mod serial; mod sound; mod system; mod timer; -pub use crate::{ - hardware::{Hardware, Key, Stream, VRAM_HEIGHT, VRAM_WIDTH}, - system::{run, run_debug, Config}, -}; +/// CPU state. +pub mod cpu; + +/// Debugger interface. +pub mod debug; + +/// Adaptor to register devices to MMU. +pub mod device; + +/// Decoder which evaluates each CPU instructions. +pub mod inst; + +/// Handles memory and I/O port access from the CPU. +pub mod mmu; + +/// Hardware interface, which abstracts OS-specific functions. +mod hardware; + +pub use crate::hardware::{Hardware, Key, Stream, VRAM_HEIGHT, VRAM_WIDTH}; +pub use crate::system::{run, run_debug, Config, System}; diff --git a/core/src/mbc.rs b/core/src/mbc.rs index 9ce8b01..65b65b3 100644 --- a/core/src/mbc.rs +++ b/core/src/mbc.rs @@ -525,6 +525,7 @@ impl Mbc5 { } } +#[allow(unused)] struct HuC1 { rom: Vec, } diff --git a/core/src/mmu.rs b/core/src/mmu.rs index 4bffe10..9fd9594 100644 --- a/core/src/mmu.rs +++ b/core/src/mmu.rs @@ -1,28 +1,43 @@ use alloc::rc::Rc; use alloc::{vec, vec::Vec}; use hashbrown::HashMap; -use log::*; +/// The variants to control memory read access from the CPU. pub enum MemRead { + /// Replaces the value passed from the memory to the CPU. Replace(u8), + /// Shows the actual value passed from the memory to the CPU. PassThrough, } +/// The variants to control memory write access from the CPU. pub enum MemWrite { + /// Replaces the value to be written by the CPU to the memory. Replace(u8), + /// Allows to write the original value from the CPU to the memory. PassThrough, + /// Discard the write access from the CPU. Block, } +/// The handler to intercept memory access from the CPU. pub trait MemHandler { + /// The function is called when the CPU attempts to read from the memory. fn on_read(&self, mmu: &Mmu, addr: u16) -> MemRead; + /// The function is called when the CPU attempts to write to the memory. fn on_write(&self, mmu: &Mmu, addr: u16, value: u8) -> MemWrite; } +/// The handle of a memory handler. #[derive(Clone, PartialEq, Eq, Hash)] pub struct Handle(u64); +/// The memory management unit (MMU) +/// +/// This unit holds a memory byte array which represents address space of the memory. +/// It provides the logic to intercept access from the CPU to the memory byte array, +/// and to modify the memory access behaviour. pub struct Mmu { ram: Vec, handles: HashMap, @@ -31,6 +46,7 @@ pub struct Mmu { } impl Mmu { + /// Create a new MMU instance. pub fn new() -> Mmu { Mmu { ram: vec![0u8; 0x10000], @@ -48,6 +64,7 @@ impl Mmu { Handle(handle) } + /// Add a new memory handler. pub fn add_handler(&mut self, range: (u16, u16), handler: T) -> Handle where T: MemHandler + 'static, @@ -72,6 +89,7 @@ impl Mmu { handle } + /// Remove a memory handler. #[allow(unused)] pub fn remove_handler(&mut self, handle: &Handle) where @@ -90,6 +108,7 @@ impl Mmu { } } + /// Reads one byte from the given address in the memory. pub fn get8(&self, addr: u16) -> u8 { if let Some(handlers) = self.handlers.get(&addr) { for (_, handler) in handlers { @@ -108,6 +127,7 @@ impl Mmu { } } + /// Writes one byte at the given address in the memory. pub fn set8(&mut self, addr: u16, v: u8) { if let Some(handlers) = self.handlers.get(&addr) { for (_, handler) in handlers { @@ -130,12 +150,14 @@ impl Mmu { } } + /// Reads two bytes from the given addresss in the memory. pub fn get16(&self, addr: u16) -> u16 { let l = self.get8(addr); let h = self.get8(addr + 1); (h as u16) << 8 | l as u16 } + /// Writes two bytes at the given address in the memory. pub fn set16(&mut self, addr: u16, v: u16) { self.set8(addr, v as u8); self.set8(addr + 1, (v >> 8) as u8); diff --git a/core/src/system.rs b/core/src/system.rs index cc6a2e0..44cb900 100644 --- a/core/src/system.rs +++ b/core/src/system.rs @@ -13,21 +13,22 @@ use crate::mmu::Mmu; use crate::serial::Serial; use crate::sound::Sound; use crate::timer::Timer; -use alloc::vec::Vec; use log::*; +/// Configuration of the emulator. pub struct Config { - /// CPU frequency + /// CPU frequency. pub(crate) freq: u64, - /// Cycle sampling count in CPU frequency controller + /// Cycle sampling count in the CPU frequency controller. pub(crate) sample: u64, - /// Delay unit in CPU frequency controller + /// Delay unit in CPU frequency controller. pub(crate) delay_unit: u64, - /// Don't adjust CPU frequency + /// Don't adjust CPU frequency. pub(crate) native_speed: bool, } impl Config { + /// Create the default configuration. pub fn new() -> Self { let freq = 4194300; // 4.1943 MHz Self { @@ -38,116 +39,180 @@ impl Config { } } + /// Set the CPU frequency. pub fn freq(mut self, freq: u64) -> Self { self.freq = freq; self } + /// Set the sampling count of the CPU frequency controller. pub fn sample(mut self, sample: u64) -> Self { self.sample = sample; self } + /// Set the delay unit. pub fn delay_unit(mut self, delay: u64) -> Self { self.delay_unit = delay; self } + /// Set the flag to run at native speed. pub fn native_speed(mut self, native: bool) -> Self { self.native_speed = native; self } } -pub fn run(cfg: Config, rom: Vec, hw: T) { - run_inner(cfg, rom, hw, Debugger::empty()) -} - -pub fn run_debug( +/// Represents the entire emulator context. +pub struct System { cfg: Config, - rom: Vec, - hw: T, - dbg: D, -) { - run_inner(cfg, rom, hw, dbg) + hw: HardwareHandle, + fc: FreqControl, + cpu: Cpu, + mmu: Option, + dbg: Device, + ic: Device, + gpu: Device, + joypad: Device, + timer: Device, + serial: Device, + dma: Device, } -fn run_inner( - cfg: Config, - rom: Vec, - hw: T, - dbg: D, -) { - info!("Initializing..."); +impl System +where + D: Debugger + 'static, +{ + /// Create a new emulator context. + pub fn new(cfg: Config, rom: &[u8], hw: T, dbg: D) -> Self + where + T: Hardware + 'static, + { + info!("Initializing..."); - let hw = HardwareHandle::new(hw); + let hw = HardwareHandle::new(hw); - let mut fc = FreqControl::new(hw.clone(), &cfg); + let mut fc = FreqControl::new(hw.clone(), &cfg); - let dbg = Device::mediate(dbg); - let mut cpu = Cpu::new(); - let mut mmu = Mmu::new(); - let sound = Device::new(Sound::new(hw.clone())); - let ic = Device::new(Ic::new()); - let irq = ic.borrow().irq().clone(); - let gpu = Device::new(Gpu::new(hw.clone(), irq.clone())); - let joypad = Device::new(Joypad::new(hw.clone(), irq.clone())); - let timer = Device::new(Timer::new(irq.clone())); - let serial = Device::new(Serial::new(hw.clone(), irq.clone())); - let mbc = Device::new(Mbc::new(hw.clone(), rom)); - let cgb = Device::new(Cgb::new()); - let dma = Device::new(Dma::new()); + let dbg = Device::mediate(dbg); + let cpu = Cpu::new(); + let mut mmu = Mmu::new(); + let sound = Device::new(Sound::new(hw.clone())); + let ic = Device::new(Ic::new()); + let irq = ic.borrow().irq().clone(); + let gpu = Device::new(Gpu::new(hw.clone(), irq.clone())); + let joypad = Device::new(Joypad::new(hw.clone(), irq.clone())); + let timer = Device::new(Timer::new(irq.clone())); + let serial = Device::new(Serial::new(hw.clone(), irq.clone())); + let mbc = Device::new(Mbc::new(hw.clone(), rom.to_vec())); + let cgb = Device::new(Cgb::new()); + let dma = Device::new(Dma::new()); - mmu.add_handler((0x0000, 0xffff), dbg.handler()); + mmu.add_handler((0x0000, 0xffff), dbg.handler()); - mmu.add_handler((0xc000, 0xdfff), cgb.handler()); - mmu.add_handler((0xff4d, 0xff4d), cgb.handler()); - mmu.add_handler((0xff56, 0xff56), cgb.handler()); - mmu.add_handler((0xff70, 0xff70), cgb.handler()); + mmu.add_handler((0xc000, 0xdfff), cgb.handler()); + mmu.add_handler((0xff4d, 0xff4d), cgb.handler()); + mmu.add_handler((0xff56, 0xff56), cgb.handler()); + mmu.add_handler((0xff70, 0xff70), cgb.handler()); - mmu.add_handler((0x0000, 0x7fff), mbc.handler()); - mmu.add_handler((0xff50, 0xff50), mbc.handler()); - mmu.add_handler((0xa000, 0xbfff), mbc.handler()); - mmu.add_handler((0xff10, 0xff3f), sound.handler()); + mmu.add_handler((0x0000, 0x7fff), mbc.handler()); + mmu.add_handler((0xff50, 0xff50), mbc.handler()); + mmu.add_handler((0xa000, 0xbfff), mbc.handler()); + mmu.add_handler((0xff10, 0xff3f), sound.handler()); - mmu.add_handler((0xff46, 0xff46), dma.handler()); + mmu.add_handler((0xff46, 0xff46), dma.handler()); - mmu.add_handler((0x8000, 0x9fff), gpu.handler()); - mmu.add_handler((0xff40, 0xff55), gpu.handler()); - mmu.add_handler((0xff68, 0xff6b), gpu.handler()); + mmu.add_handler((0x8000, 0x9fff), gpu.handler()); + mmu.add_handler((0xff40, 0xff55), gpu.handler()); + mmu.add_handler((0xff68, 0xff6b), gpu.handler()); - mmu.add_handler((0xff0f, 0xff0f), ic.handler()); - mmu.add_handler((0xffff, 0xffff), ic.handler()); - mmu.add_handler((0xff00, 0xff00), joypad.handler()); - mmu.add_handler((0xff04, 0xff07), timer.handler()); - mmu.add_handler((0xff01, 0xff02), serial.handler()); + mmu.add_handler((0xff0f, 0xff0f), ic.handler()); + mmu.add_handler((0xffff, 0xffff), ic.handler()); + mmu.add_handler((0xff00, 0xff00), joypad.handler()); + mmu.add_handler((0xff04, 0xff07), timer.handler()); + mmu.add_handler((0xff01, 0xff02), serial.handler()); - dbg.borrow_mut().init(&mmu); + dbg.borrow_mut().init(&mmu); - info!("Starting..."); + info!("Starting..."); - fc.reset(); + fc.reset(); - while hw.get().borrow_mut().sched() { + let mmu = Some(mmu); + + Self { + cfg, + hw, + fc, + cpu, + mmu, + dbg, + ic, + gpu, + joypad, + timer, + serial, + dma, + } + } + + fn step(&mut self, mut mmu: Mmu) -> Mmu { { - let mut dbg = dbg.borrow_mut(); + let mut dbg = self.dbg.borrow_mut(); dbg.check_signal(); - dbg.take_cpu_snapshot(cpu.clone()); + dbg.take_cpu_snapshot(self.cpu.clone()); dbg.on_decode(&mmu); } - let mut time = cpu.execute(&mut mmu); + let mut time = self.cpu.execute(&mut mmu); + + time += self.cpu.check_interrupt(&mut mmu, &self.ic); - time += cpu.check_interrupt(&mut mmu, &ic); + self.dma.borrow_mut().step(&mut mmu); + self.gpu.borrow_mut().step(time, &mut mmu); + self.timer.borrow_mut().step(time); + self.serial.borrow_mut().step(time); + self.joypad.borrow_mut().poll(); - dma.borrow_mut().step(&mut mmu); - gpu.borrow_mut().step(time, &mut mmu); - timer.borrow_mut().step(time); - serial.borrow_mut().step(time); - joypad.borrow_mut().poll(); + if !self.cfg.native_speed { + self.fc.adjust(time); + } + + mmu + } - if !cfg.native_speed { - fc.adjust(time); + /// Run a single step of emulation. + /// This function needs to be called repeatedly until it returns `false`. + /// Returning `false` indicates the end of emulation, and the functions shouldn't be called again. + pub fn poll(&mut self) -> bool { + if !self.hw.get().borrow_mut().sched() { + return false; } + + let mmu = self.mmu.take().unwrap(); + self.mmu = Some(self.step(mmu)); + + true } } + +/// Run the emulator with the given configuration. +pub fn run(cfg: Config, rom: &[u8], hw: T) { + run_inner(cfg, rom, hw, Debugger::empty()) +} + +/// Run the emulator with the given configuration and debugger. +pub fn run_debug( + cfg: Config, + rom: &[u8], + hw: T, + dbg: D, +) { + run_inner(cfg, rom, hw, dbg) +} + +fn run_inner(cfg: Config, rom: &[u8], hw: T, dbg: D) { + let mut sys = System::new(cfg, rom, hw, dbg); + while sys.poll() {} +}