mirror of
https://github.com/ethauvin/chip-8.git
synced 2025-04-25 00:37:13 -07:00
83 lines
3 KiB
Markdown
83 lines
3 KiB
Markdown
# chip8
|
|
## A [Chip-8](http://www.cs.columbia.edu/~sedwards/classes/2016/4840-spring/designs/Chip8.pdf) emulator written in Kotlin
|
|
|
|
<p align="center">
|
|
<img src="https://github.com/cbeust/chip8/blob/master/pics/space-invaders-2.gif?raw=true"/>
|
|
</p>
|
|
|
|
# How to run
|
|
|
|
```
|
|
$ ./gradlew run
|
|
```
|
|
|
|
The emulator will load with Space Invaders by default (press 5 to start the game, then 4/5/6 to move around and shoot). Open a new rom by clicking on the "Open rom..." button.
|
|
|
|
You can pause the emulator at any time (key '`p`'), which will update the disassembly window to show the next instructions about to be executed. You can also adjust the clock speed to make the emulator go slower or faster.
|
|
|
|
# Architecture
|
|
|
|
The game creates a [`Computer`](https://github.com/cbeust/chip8/blob/master/src/main/kotlin/com/beust/chip8/Computer.kt) object which is made of a `Display`, `Keyboard`, `FrameBuffer` and `Cpu`.
|
|
|
|
## Cpu
|
|
|
|
The CPU reads a new instruction (the next two bytes extracted at the program counter location) at a fixed rate
|
|
which defines the clock speed. Two timers are needed: one for the CPU and one for the device timer register,
|
|
called `DT`, which needs to tick at 60 Hz according to [the spec](http://www.cs.columbia.edu/~sedwards/classes/2016/4840-spring/designs/Chip8.pdf). Since there is no specific definition for the CPU clock, I used the timing diagram from the document
|
|
to set it at around 500Hz:
|
|
|
|
```kotlin
|
|
// CPU clock: around 500 Hz by default
|
|
cpuFuture = executor.scheduleAtFixedRate(cpuTick, 0, 1_000_000L / cpuClockHz, TimeUnit.MICROSECONDS)
|
|
|
|
// Delay Timer: 60 Hz by spec
|
|
timerFuture = executor.scheduleAtFixedRate(timerTick, 0, 16L, TimeUnit.MILLISECONDS)
|
|
```
|
|
|
|
<p align="center">
|
|
<img width="50%" src="https://github.com/cbeust/chip8/blob/master/pics/tetris-1.png?raw=true"/>
|
|
</p>
|
|
|
|
The next two bytes are then masked and turned into instructions. All the op codes can be found in the `[Ops.kt](https://github.com/cbeust/chip8/blob/master/src/main/kotlin/com/beust/chip8/Ops.kt)` file. Here is an example:
|
|
|
|
```kotlin
|
|
/**
|
|
* 7xkk
|
|
* Set Vx = Vx + kk
|
|
*/
|
|
class Add(c: Computer, n: Nibbles): Op(c, n) {
|
|
override fun run() { cpu.V[x] = unsigned(cpu.V[x] + kk) }
|
|
override fun toString() = "ADD V$x, $kk"
|
|
}
|
|
```
|
|
|
|
## Display
|
|
|
|
The `Display` is a simple interface which allows multiple strategies to render the frame buffer:
|
|
|
|
```kotlin
|
|
interface Display {
|
|
val pane: Pane
|
|
fun draw(frameBuffer: IntArray)
|
|
fun clear(frameBuffer: IntArray)
|
|
}
|
|
```
|
|
|
|
For example, here is a text based renderer:
|
|
|
|
<p align="center">
|
|
<img width="50%" src="https://github.com/cbeust/chip8/blob/master/pics/space-invaders-text.png?raw=true"/>
|
|
</p>
|
|
|
|
The emulator window will resize gracefully:
|
|
|
|
<p align="center">
|
|
<img width="50%" src="https://github.com/cbeust/chip8/blob/master/pics/space-invaders-small.png?raw=true"/>
|
|
</p>
|
|
|
|
You can also easily alter other aspects of the renderer:
|
|
|
|
<p align="center">
|
|
<img width="50%" src="https://github.com/cbeust/chip8/blob/master/pics/space-invaders-colors.png?raw=true"/>
|
|
</p>
|
|
|