• Stars
    star
    244
  • Rank 165,885 (Top 4 %)
  • Language
    Rust
  • Created about 4 years ago
  • Updated over 1 year ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

A Gameboy emulator written in Rust

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

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
  • Using Wrapping types for the emulated arithmetic

More Repositories

1

Synthic

Automatically generate gameboy music using machine learning
Rust
14
star
2

PiFC

Building a Raspberry Pi flight controller
Rust
12
star
3

klee-rust

A interface for KLEE on the Rust language
Rust
9
star
4

c8hardcaml

An implementation of a CHIP-8 machine for FPGAs in Hardcaml with a custom assembler for writing test programs
OCaml
8
star
5

ocamlGpiod

Ocaml binding auto-generation for libgpiod
OCaml
5
star
6

Puffer

Packet level firewall for Android
C++
5
star
7

Dawn

Dawn is a project to create a simple, working x86 operating system. View the readme or the documentation for more information.
C
4
star
8

ocamlIoUringAsyncBackend

An attempt at an IO uring backend for Async
OCaml
4
star
9

CHIP-9

A CHIP-8 emulator written in Rust
Rust
4
star
10

rust-tiff

Parse TIFF data in Rust
Rust
2
star
11

rustTorrent

Rust Torrent Library
Rust
2
star
12

Scribble

Scribble is a scripting language, register-based IR, and interpreter designed to be embedded in large applications. The language has a simple syntax, is garbage collected, and supports type inference. It is designed in a way that enables quick integration with large C++ projects.
C++
2
star
13

Cipher

Playground for crypto experiments in Rust
Rust
1
star
14

EncryptThing

Simple AES decrypt/encrypt in Rust
Rust
1
star
15

LJIT

Implementing simple JIT compiler
C++
1
star
16

Simplex

A simple simplex solver
C
1
star
17

SimpleLog

Simple logging library written in Rust used on two of my packages
Rust
1
star
18

risc-v-emulator

A WIP R32I emulator in Rust
Rust
1
star
19

Brainfuck-Interpreter

A Haskell Brainfuck Interpreter
Haskell
1
star
20

New-Worlds

Implementing a basic MUD in Rust to experiment with TCP
Rust
1
star
21

ocamlLlvmLispJit

A small LISP-like programming language with a JIT engine backed by LLVM
OCaml
1
star
22

Parrot2

A static website generator written in Ocaml
OCaml
1
star
23

mlYcPreferences

A tool that guesses if you are interested in an article based on previous inputs. The preferences classifier works by encoding the webpage title and FQDN using a pre-trained NLP model and then using the outputs of the pre-trained models to train a classifier.
Python
1
star
24

Pico-W-Tetris

An implementation of Tetris to run on a Pico W with an I2C screen. The core is all [#no_std].
Rust
1
star
25

RustMatrix

Simple Rust Matrix data structure implementation
Rust
1
star
26

rustRingbuffer

An efficient single-producer single-consumer ringbuffer in Rust that can be shared between threads
Rust
1
star
27

Parrot

A static website generator that generates a website from a HTML skeleton. Includes easy extension through template strings and automatic image web optimization. Written entirely in Haskell
Haskell
1
star