A simple Game Boy emulator written in Rust. NB! This is a work in progress. It is not possible to play games on this emulator yet.
The main loop of the emulator performs one tick of the system clock.
The subsystems of the emulator, like e.g. the CPU or the video system, are responsible for updating themselves through a tick
function that each subsystem must implement.
Each subsystem has to keep track of how many cycles their own operations are to take.
Subsystems are the units that do something each cycle. They all need access to the Game Boy memory.
Subsystem | Description |
---|---|
CPU | Fetches, decodes and executes instructions. |
DMA | Transfers data. |
Video | Renders the output video buffer, line by line. |
Audio | Renders the output audio buffer. |
Timer | Triggers an interrupt after a given number of cycles. |
Interrupts | Dispatch interrupts. |
Serial I/O | Not implemented. |
At tick, it checks if the current instruction has finished executing. If not, nothing happens. Else, the next instruction is fetched, decoded and executed.
Each instruction takes the form of an opcode, plus up to two operands.
TODO
TODO
The audio subsystem consists of four sound generators:
- rectangle wave with sweep and envelope,
- rectangle wave with envelope,
- digital wave,
- white noise with envelope.
TODO
TODO
An object implementing Source
can output either a u8
or u16
.
An object implementing Target
accepts a u8
or u16
.
An instruction has one or two operands that can be either
- [OK] immediate (data follows instruction directly in memory),
- register (data in register), or
- indirect (data in memory).
Depending on how the address is stored, indirect can be either
- indirect register (address in word register or
0xFF00 + C
), - indirect immediate (address is immediate word), or
- indirect high immediate (address is
0xFF00
+ immediate byte).
Register and indirect can be used both as source and target operands, but immediate only as source.
Instructions are implemented as functions that take operands as arguments.
The operands implement the Source
and/or Target
traits.
This way, the instructions can be written in a general way, abstracting memory access details.
The instructions are member functions of the CPU object, which owns the registers, so the instructions always have access to the registers, but the other data that the instructions operate on, like memory, have to come in via the operands. This can lead to problems if e.g. the instruction is going to both read and write to memory, which would require the two operands to both borrow memory, but one of them borrowing mutably.
To avoid this, the source operand always contains a copy of the data, and doesn't borrow anything.