A Gameboy emulator written in Rust

Overview

Mimic

An open source Gameboy emulator written in Rust that can use a command line interface as a screen and input device. The project is an attempt to make an approachable emulator for the Gameboy that can be used to explain the concepts required in emulating a system without overwhelming the reader. The core logic is all in safe Rust, there is no JIT recompiler, and screen / IO logic is kept separate from the emulation core to reduce complexity. As such, it does not perform ideally, but the Gameboy is an old system so ideal performance is not necessary to run games at full speed.

Usage

cargo run --release -- --rom PATH --cli-mode

Overview

We split our design into a set of components that roughly map to the physical components on the original hardware. These components are:

  • CPU: This structure models the fetch, decode, and execute cycle of the CPU and owns the machine registers. Hardware interrupts are also modelled in this structure.
  • Registers: A structure that stores the CPU registers and some hidden registers for emulation. This does not store the special memory registers.
  • Instructions: An implementation of each CPU opcode plus a table mapping opcodes to their implementation.
  • Clock: A structure that models the Gameboy clock.
  • PPU (Pixel-processing unit): A structure that models the Gameboy PPU, a component which takes sprite maps and data from memory and draws them to the screen. All video output on the Gameboy travels through the PPU.
  • Memory: An implementation of the hardware memory bus and cartridge emulation. Special memory registers are also stored here.

In addition to these components we have the Machine structure which brings all of these components together under a single structure with a central step instruction that moves the emulation forward by precisely one instruction (meaning a single instruction is executed and then all other components are updated so that they are up to date with the new state). The remaining files in the project, main.rs, terminal.rs, and sdl.rs provide the launch sequence and screen / terminal backends for interacting with the emulated machine.

CPU

The Gameboy uses a modified CISC (complex instruction set computer) Z80 Zilog processor at a frequency of 4.19Mhz. It features an 8-bit instruction set with a 16-bit address bus and some limited support for 16 bit arithmetic. The CPU has a complex instruction set with variable length opcodes. The first byte of every opcode indicates which of the instructions it is. As there are more than 256 instructions, there is also an extended opcode which swaps the CPU to a second instruction set for an instruction starting at the next byte.

The CPU is represented in Mimic through two jump tables that are constructed at startup, one for the main opcode set and one for the extended set. Each entry in the jump table contains a pointer to an execute function which takes the current registers and memory and implements the opcode. A secret register is used to keep track of whether the previous instruction executed was the extend instruction that moves execution to the extended opcode set. The processor steps (executes an instruction) by selecting the next execute function from either the main or extended table depending on the hidden register and then executing it.

Each entry in the table also has metadata to indicate how many cycles that instruction takes to emulate, and the total number of cycles executed is tracked in hidden registers. We need to track this because different components in the Gameboy operate at fixed cycle rates and keeping them in sync with the executed instructions is crucial to accurate emulation.

Registers

There are 8 general purpose registers 8-bit registers B, C, A, F, D, E, H, L on the device. These registers can also be addressed as 16-bit registers for some instructions as BC, AF, DE, and HL. There are also special register PC and SP for the program counter (the memory address of the current instruction) and the stack pointer. Not all registers can be used in all operations, and some are used to store side effects of opcodes. The A register is used as the accumulator, and is usually the destination register for the result of arithmetic operations. The F register stores the flags after some opcodes, encoded as a bit vector that tells the program is the previous opcode carried, half carried, was negative, or was zero.

Memory

The Gameboy uses an 8-bit Z80 CPU with a 16-bit memory addressing scheme. The address space is used to access system ROM, cartridge ROM, system RAM, cartridge RAM and to interface with other systems on the device through special memory registers. The console includes a small 256 byte ROM containing the boot up sequence code which scrolls a Nintendo logo across the screen and then does some primitive error checking on the cartridge. This ROM is unmapped from the address space after the initial boot sequence. There is also 8kb of addressable internal RAM and 8kb of video ram for sprites on the device. There are both mapped into fixed locations in the address space.

Programs are read from cartridges that are physically connected to the device and are directly addressable rather than being loaded into memory. The cartridge memory is access through reads to specific regions of memory which the device will automatically treat as a cartridge read. For example a read to 0x0 will access memory 0x0 in the first bank of the cartridge ROM while a write to 0x8000 will access the first byte of the on-board video RAM. The cartridge based design is useful because it allows the available RAM to be used only for program memory, while a system that has to load the program into memory would have reduced capability due to the reduction in usable RAM for program state.

The cartridge based design can be used to expand the available ROM and RAM though this increased the price of physical cartridges. Since the combined ROM and RAM of expanded cartridges cannot be addressed in 16 bits ROM banking where addressable parts of the cartridge ROM or RAM are remapped through writes to specific memory addresses. This requires careful programming since the program code being executed or some data required could be located in a bank that is remapped. This can be used to increase the ROM or RAM on system to 32kb.

Memory is represented in Mimic through a GameboyState structure which tracks the memory map, plus a series of ROM or RAM structures. The special memory registers are hardcoded into the top level structure at their given addresses and with the given read/write rules. The GameboyState routes ROM/RAM read and writes to corresponding structures for processing. ROM banking is also tracked in the this top level structure.

Interrupts

The Gameboy uses CPU interrupts to notify the running software of hardware events. These events include screen events, timer events, and input events. When an interrupt is raised, a bit in the interrupted memory register is set to indicate it. If interrupts are enabled (toggled with a dedicated instruction) then the CPU will check if the corresponding bit for that interrupt is set in the interrupts enabled memory register. If both bits are set then the current PC will be pushed to the stack and the PC will be set to a fixed interrupt-specific location and interrupts will be disabled. The code at that location is then run (similar to a call instruction) and interrupts are re-enabled upon return.

PPU

The pixel processing unit (PPU) finds the sprites referenced in the sprite, window, and background maps and draws them to the device screen. The PPU operates concurrently with the CPU and is the only component that can draw to the screen. Interaction with the PPU comes only through updates to the sprite memory, sprite maps, or special memory registers. Feedback for the CPU comes through writes to special registers and hardware interrupts.

Clock

The Gameboy clock interacts with the CPU through special memory registers or through CPU interrupts. There are two clocks, one which ticks at a constant frequency and another which can be configured through writes to a special register. Since the clock is tied to the cycle rate of the CPU, and not the actual time, we implement the clock in Mimic through a structure that tracks the number of cycles the CPU has performed and updates it's own values accordingly.

Screenshots

Screenshot Screenshot Screenshot Screenshot

Working

  • System memory map
  • Core instruction set
  • Pixel-processing unit
  • Background rendering
  • Sprite rendering
  • Interrupts
  • Input
  • Clock
  • Memory Banking (Rudimentry MBC1, MBC3)

TODO

  • Sound
  • Game compatibility
  • Cartridge Save
You might also like...
Snake implemented in rust.
Snake implemented in rust.

rsnake - An implementation of classic snake in rust This game was built using the piston_window window wrapper. Download the game If youre using mac-o

An implementation of Sokoban in Rust
An implementation of Sokoban in Rust

sokoban-rs This is an implementation of Sokoban in the Rust Programming Language. An example level: Build Instructions Before building sokoban-rs, you

A personal etude into rust software (RPG-it's more fun to debug) development: Tales of the Great White Moose

TGWM (Tales of the Great White Moose) NB: Currently compiles. Should compile and run on both 1.28.0 and 1.31.1 if the Cargo.lock files are deleted. A

😠⚔️😈 A minimalistic 2D turn-based tactical game in Rust
😠⚔️😈 A minimalistic 2D turn-based tactical game in Rust

Zemeroth is a turn-based hexagonal tactical game written in Rust. Support: patreon.com/ozkriff News: @ozkriff on twitter | ozkriff.games | facebook |

This is a simple implementation of the classic snake game in rust
This is a simple implementation of the classic snake game in rust

My snake game Looks like this. This is with Roboto Mono Nerd Font. If you use a different font it may look different or distorted. Install rust In ord

The video game for Fonts of Power. A tabletop roleplaying game made in Rust with Bevy!

The code and rules for Fonts of Power, a tactical TTRPG / video game about exploring magical places. You can follow its development in our Discord ser

A refreshingly simple data-driven game engine built in Rust

What is Bevy? Bevy is a refreshingly simple data-driven game engine built in Rust. It is free and open-source forever! WARNING Bevy is still in the ve

Rust library to create a Good Game Easily

ggez What is this? ggez is a Rust library to create a Good Game Easily. The current version is 0.6.0-rc0. This is a RELEASE CANDIDATE version, which m

Minesweeper game developed with Rust, WebAssembly (Wasm), and Canvas
Minesweeper game developed with Rust, WebAssembly (Wasm), and Canvas

👉 click here to play the game 👈 Minesweeper game Revealing all the cells without hitting the mines is the task. Each number in the cell denotes how

Comments
  • CLI mode is flashing

    CLI mode is flashing

    I'm on Windows 10 and when trying to run using the --cli-mode switch, there is very noticeable flashing as it renders in a terminal. I did not observe this behavior in the default graphics mode. I thought it may be a defect with Windows Terminal or powershell/pwsh/cmd, but I'm seeing the same behavior with WSL2 on bash and Alacritty.

    opened by Halkcyon 12
  • Some lSDL2 not found error

    Some lSDL2 not found error

    Hi, I am trying to run this on Ubuntu 20.04 on a VM I get the following error

    note: /usr/bin/ld: cannot find -lSDL2
    error: could not compile `gb_int` due to previous error
    

    Please help with this

    opened by VaibhavDS19 2
  •  unresolved import `clap::Clap`

    unresolved import `clap::Clap`

    hey cool project. Currently it doesn't compile.


    error[E0432]: unresolved import clap::Clap --> src/main.rs:24:25 | 24 | use clap::{AppSettings, Clap}; | ^^^^ no Clap in the root

    error: cannot determine resolution for the derive macro Clap --> src/main.rs:28:10 | 28 | #[derive(Clap)] | ^^^^ | = note: import resolution is stuck, try simplifying macro imports

    error: cannot find attribute clap in this scope --> src/main.rs:29:3 | 29 | #[clap(version = "1.0", author = "Blake Loring [email protected]")] | ^^^^ | = note: clap is in scope, but it is a crate, not an attribute

    error: cannot find attribute clap in this scope --> src/main.rs:30:3 | 30 | #[clap(setting = AppSettings::ColoredHelp)] | ^^^^ | = note: clap is in scope, but it is a crate, not an attribute

    error: cannot find attribute clap in this scope --> src/main.rs:33:5 | 33 | #[clap(short, long)] | ^^^^ | = note: clap is in scope, but it is a crate, not an attribute

    error: cannot find attribute clap in this scope --> src/main.rs:35:5 | 35 | #[clap(short, long)] | ^^^^ | = note: clap is in scope, but it is a crate, not an attribute

    error: cannot find attribute clap in this scope --> src/main.rs:37:5 | 37 | #[clap(short, long)] | ^^^^ | = note: clap is in scope, but it is a crate, not an attribute

    error: cannot find attribute clap in this scope --> src/main.rs:39:5 | 39 | #[clap(long)] | ^^^^ | = note: clap is in scope, but it is a crate, not an attribute

    error: cannot find attribute clap in this scope --> src/main.rs:41:5 | 41 | #[clap(long)] | ^^^^ | = note: clap is in scope, but it is a crate, not an attribute

    error: cannot find attribute clap in this scope --> src/main.rs:43:5 | 43 | #[clap(short, long)] | ^^^^ | = note: clap is in scope, but it is a crate, not an attribute

    error: cannot find attribute clap in this scope --> src/main.rs:45:5 | 45 | #[clap(short, long)] | ^^^^ | = note: clap is in scope, but it is a crate, not an attribute

    error: cannot find attribute clap in this scope --> src/main.rs:47:5 | 47 | #[clap(long, default_value = "4")] | ^^^^ | = note: clap is in scope, but it is a crate, not an attribute

    warning: unused import: AppSettings --> src/main.rs:24:12 | 24 | use clap::{AppSettings, Clap}; | ^^^^^^^^^^^ | = note: #[warn(unused_imports)] on by default

    error[E0599]: no function or associated item named parse found for struct Opts in the current scope --> src/main.rs:53:26 | 31 | struct Opts { | ----------- function or associated item parse not found for this ... 53 | let opts: Opts = Opts::parse(); | ^^^^^ function or associated item not found in Opts | = help: items from traits can only be used if the trait is implemented and in scope = note: the following trait defines an item parse, perhaps you need to implement it: candidate #1: Parser

    warning: unused import: Write --> src/terminal.rs:12:29 | 12 | use std::io::{self, stdout, Write}; | ^^^^^

    Some errors have detailed explanations: E0432, E0599. For more information about an error, try rustc --explain E0432. warning: gb_int (bin "gb_int") generated 2 warnings error: failed to compile gb_int v0.1.0 (https://github.com/jawline/Mimic.git#9c1ff27d), intermediate artifacts can be found at /tmp/cargo-installzK8Jhy

    Caused by: could not compile gb_int due to 13 previous errors; 2 warnings emitted


    If it makes any difference i am installing by:

    cargo install --git https://github.com/jawline/Mimic.git

    opened by heycitizen 1
  • DRAFT: Use console_engine instead of crossterm

    DRAFT: Use console_engine instead of crossterm

    Hi !

    I noticed one of your dependencies included the crate console_engine (I'm the creator of it, by the way). Out of curiosity, and for fun, I wanted to try out converting the actual terminal application to use this crate instead of crossterm (which my crate is based of).

    I just pushed this PR as a draft so you can take a quick look at it. There's sadly a dirty hack somewhere to add compatibility with the excellent drawille crate. Aside from that, it seems to work pretty well. I'm curious if the recent "flashing" issue is still showing with this change.
    One thing that console_engine does is printing only differences between frames, to gain performances since drawing on some terminals can be really slow.

    Feel free to close this PR if you're not interested. 😄
    I used this project to improve, fix and release the event feature of my crate, so it's not totally a loss 😉

    By the way, while working on this project, I noticed a bunch of warnings from rust-analyzer. If you don't mind, I'll soon enough create a new PR to fix all of these. Anyway, Mimic is a really nice project you got, good job!

    Kind regards,

    opened by VincentFoulon80 5
Owner
Blake Loring
Blake Loring
4fun open-source Cave Story reimplementation written in Rust

doukutsu-rs Download latest Nightly builds (Requires being logged in to GitHub) A re-implementation of Cave Story (Doukutsu Monogatari) engine written

null 564 Jan 1, 2023
A Doom Renderer written in Rust.

Rust Doom A little Doom 1 & 2 Renderer written in Rust. Mostly written while I was learning the language about 2 years ago, so it might not the best e

Cristi Cobzarenco 2.2k Jan 1, 2023
ASCII terminal hexagonal map roguelike written in Rust

rhex Contributors welcome! Rhex is looking for contributors. See Contributing page for details. Introduction Simple ASCII terminal hexagonal map rogue

Dawid Ciężarkiewicz 137 Dec 2, 2022
⬡ Zone of Control is a hexagonal turn-based strategy game written in Rust. [DISCONTINUED]

Zone of Control The project is discontinued Sorry, friends. ZoC is discontinued. See https://ozkriff.github.io/2017-08-17--devlog.html Downloads Preco

Andrey Lesnikóv 354 Nov 14, 2022
The classic tetris game written in Rust using ncurses

tetris.rs This is the classic tetris game I wrote to have a bit of fun with Rust. Installation and playing cargo install --

null 71 Jul 25, 2022
Data-oriented and data-driven game engine written in Rust

What is Amethyst? Amethyst is a data-driven and data-oriented game engine aiming to be fast and as configurable as possible. Principles These principl

Amethyst Engine 7.9k Dec 31, 2022
A solver for the popular Wordle game written in Rust.

Wordle Solver A solver for the popular Wordle game written in Rust. It does not attempt to be the most efficient solver possible but tries to avoid us

William Hoggarth 2 Jul 1, 2022
Simple wordle clone written in Rust.

wordle-rs A small wordle clone built in rust. Note: There are a lot of weird words I've never even heard in the dictionary file. If anyone knows a bet

Dheeraj Prakash 1 Feb 2, 2022
Angolmois BMS player, Rust edition

Angolmois Rust Edition This is a direct, one-to-one translation of Angolmois to Rust programming language. Angolmois is a BM98-like minimalistic music

Kang Seonghoon 95 Oct 20, 2022
A roguelike game in Rust

A fantasy deathcrawl in Rust Work in progress. To run, with Rust compiler and Cargo package manager installed: cargo run --release When building on W

Risto Saarelma 347 Nov 21, 2022