jawline/mimic

An open source Gameboy emulator written in Rust that can use a command line interface...

An open source Gameboy emulator written in Rust that can use a command line interface as a screen and input device

Mimic

. 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 such an old system that ideal performance is not necessary to run games at full framerates.

Usage

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

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.

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

Working

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

TODO

  • Sound
  • The emulator will not run in Debug mode because Rust errors on unsigned integer arithmetic over/underflow. This can be alleviated using the Wrapping struct but requires explicit wrapping of every literal value. wrapping_add and wrapping_sub could be used on every instruction, but neither solution leads to readable code and I would prefer a solution that leaves the base logic understandable.
  • Improve compatibility
Issues

Collection of the latest Issues

heycitizen

heycitizen

Comment Icon1

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 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

Information - Updated Jun 25, 2022

Stars: 222
Forks: 9
Issues: 2

Repositories & Extras

RGB (Rust Game Boy) is a simple emulator for the original game boy and the...

RGB (Rust Game Boy) is a simple emulator for the original game boy and the color game boy

RGB (Rust Game Boy) is a simple emulator for the original game boy and the...

A Gameboy emulator written in Rust

Can launch and play simple games

A Gameboy emulator written in Rust

Frosty is a GameBoy emulator written in rust

You can also hold TAB to enable turbo, which will disable the frame limiter

Frosty is a GameBoy emulator written in rust

🕹️ A work-in-progress GameBoy emulator written in Rust

This project is just a work-in-progress

🕹️ A work-in-progress GameBoy emulator written in Rust

Mizu is an accurate Gameboy emulator built in Rust

Emulating The original gameboy (DMG) and gameboy color hardware

Mizu is an accurate Gameboy emulator built in Rust

Rust GameBoy Advanced Emulator

Building this project on linux requires cmake and gtk3

Rust GameBoy Advanced Emulator

A Rust GameBoy emulator!

TetrisZelda: Link's Awakening

A Rust GameBoy emulator!

CorrodedBoy, a Gameboy Emulator written in Rust

Try out the emulator online here!

CorrodedBoy, a Gameboy Emulator written in Rust
Facebook Instagram Twitter GitHub Dribbble
Privacy