Let’s write a Chip8 emulator in Kotlin [Part 1: A simple Disassembler]

Disclaimer

This series is a means for me to learn Kotlin. As such i might misuse some features of Kotlin, not follow best practices or simply to silly things. Please always check the comment section for feedback from more knowledgable people. I’ll also have follow up articles where i refactor the code to be more idiomatic. So, make sure to check back.

Goals

Last time we setup our Kotlin project proper. This time we are going to write a simple disassembler for Chip8 ROMs!

What is Chip8?

Chip8 is a sort of virtual machine/interpreter for a simplistic instruction set from the mid-70ies, developed by Joseph Weisbecker. I won’t bore you with the history of Chip8, Wikipedia has a pretty good summary. There are a handful of games for the Chip8 which come in form of ROMs containing the raw instructions and data. E.g. a simple breakout clone called Brix:

Chip8 is conceptually composed of a few parts:

  • Memory: Chip8 has 4096 bytes of memory, linearly addressable. Byte 0x000-0x1FF contained the original interpreter and bitmaps for rendering text. Program code is stored from 0x200-0xE9F. Memory from 0xEA0-0xEFF is reserved for the call stack, internal use and other variables. Memory from 0xF00-0xFFF is reserved for the display buffer.
  • Registers: Chip8 has 16 8-bit general purpose registers called V0 to VF. The VF register is also used as a carry flag for operations such as additions or shifts. Additionally there’s a 16-bit address register called I and a programm counter called PC which are manipulated indirectly. There’s also a stack pointer called SP which can not be directly accessed.
  • Stack: only used to store return addresses when subroutines are called. The stack is not stored in memory, but in form of a 16-element array of 16-bit values, storing return addresses. This limits Chip8 to a callstack depth of 16.
  • Timers: Chip8 has two timers called delay timer and sound timer. Both get decremented every 16ms (60Hz) until they reach 0. The delay timer is used to time events in the application. The sound timer will play a sound when it reaches 0.
  • Keyboard: Chip8 uses a 16-key keypad (see here). Special instructions exist to read the keystates and wait for keypresses.
  • Display: Chip8 has a 64×32 pixel monochrome display, mapped to memory area 0xF00-0xFFF which i’ll refer to as display buffer. Each pixel is represented as one bit in the display buffer. Graphics are drawn via sprites. A sprite is a group of bytes, each bit representing a pixel (0->off, 1->on). All sprites are 8-pixels in width and 1-15 pixels in height. Each row in a sprite is hence one byte wide. Special instructions exist to clear the screen and draw sprites referenced via memory addresses. Sprites are drawn in XOR mode. When an existing bit in the display buffer gets erased by drawing a sprite, the VF is set to 1. This is used for collision detection in many games.
  • Instructions: Chip8 has 35 instructions (opcodes), each two bytes (16-bit) in size. The most significant byte is stored first. They allow simple control flow, 8-bit arithmetic and bit manipulation and interacting with timers, the keypad and the display.

Cowgod put together an excellent reference for the Chip8 architecture which i’ll use as the gold standard. You can find emulators and ROMs around the web.

Storing the Chip8 VM State

Before we dive into any kind of opcodes, we should think about how we want to keep the state of our little Chip8 VM. We need:

  • Memory. We’ll initialize this with built-in font data and copy the contents of a ROM file there starting at address 0X200.
  • Registers. We have 16 general-purpose registers V0-VF, each 8-bits wide. In addition we have PC and I, each 16-bit wide. Only the lower 12-bit are used as the address space is limited to 0x000-0xFFF. We also have a stack pointer which points at the current stack location.
  • Stack. We need 16 16-bit wide stack elements to store return addresses
  • Timers. We need to store the delay and sound timer values, both 8-bit wide
  • Keypad. We need to store the state of each of the 16 keys, a byte will do per key

This can be easily translated to a data class in Kotlin. We won’t add any behaviour to that class, it will only store state (VmState.kt):

package chip8;

data class VmState (val ram: ByteArray = ByteArray(4096),
                    val registers: ByteArray = ByteArray(16),
                    var pc: Int = 0x200,
                    var index: Int = 0,
                    var sp: Int = 0,
                    val stack: IntArray = IntArray(16),
                    var delay: Int = 0,
                    var sound: Int = 0,
                    val keys: ByteArray = ByteArray(16))

Short and sweet. Notice the use of val for reference types like the ram or registers, and var for things that we need to mutate, like pc. Kotlin will automatically generate properties with appropriate getters (for var and val) and setters (for var properties) based on the parameters of the constructor. We also use default values to initialize our properties.

We also use Ints instead of all registers and timers, except the general purpose registers and the keypad states.

Loading a ROM

For loading a ROM file, we simply read the contents of the file into our VmState#ram, starting at address 0x200. We create a top-level function in our chip8 package (main.kt):

package chip8;

import java.io.FileInputStream
import java.io.BufferedInputStream
import java.io.DataInputStream

fun loadRom(file: String): VmState {
    return DataInputStream(BufferedInputStream(FileInputStream(file))).use {
        val state = VmState()
        val rom = it.readBytes()
        System.arraycopy(rom, 0, state.ram, state.pc, rom.size)
        state
    }
}

fun main(args: Array) {
    val vmState = loadRom("roms/maze.rom")
    println(vmState.ram[0x200])
}

Kotlin Note: is there a better way to do this?

This function uses the Closable#use extension method defined by the Kotlin standard library. It will automatically close the Closable it is used on, whether an exception is thrown in the provided code block or not. This is kinda similar to Java’s try-with-resources. Note that the last expression in the block makes up the return value of the use method, which is then returned by loadRom. Very concise.

To make this code run, i had to copy a few ROMs to the project under the roms/ folder. Executing this app leaves us with a VmState that has the ROM contents loaded at address 0x200 within the state’s ram. The app simply outputs the first byte of the loaded ROM, which is 96.

Decoding the program

Once our ROM is loaded, we can start dissecting it. In this article we are going to write a simple disassembler that outputs the opcodes to the console. Let’s take a look at the instruction set (taken from http://mattmik.com/chip8.html by Matthew Mikolay).

Opcode Description
0NNN Execute machine language subroutine at address NNN
00E0 Clear the screen
00EE Return from a subroutine
1NNN Jump to address NNN
2NNN Execute subroutine starting at address NNN
3XNN Skip the following instruction if the value of register VX equals NN
4XNN Skip the following instruction if the value of register VX is not equal to NN
5XY0 Skip the following instruction if the value of register VX is equal to the value of register VY
6XNN Store number NN in register VX
7XNN Add the value NN to register VX
8XY0 Store the value of register VY in register VX
8XY1 Set VX to VX OR VY
8XY2 Set VX to VX AND VY
8XY3 Set VX to VX XOR VY
8XY4 Add the value of register VY to register VX
Set VF to 01 if a carry occurs
Set VF to 00 if a carry does not occur
8XY5 Subtract the value of register VY from register VX
Set VF to 00 if a borrow occurs
Set VF to 01 if a borrow does not occur
8XY6 Store the value of register VY shifted right one bit in register VX
Set register VF to the least significant bit prior to the shift
8XY7 Set register VX to the value of VY minus VX
Set VF to 00 if a borrow occurs
Set VF to 01 if a borrow does not occur
8XYE Store the value of register VY shifted left one bit in register VX
Set register VF to the most significant bit prior to the shift
9XY0 Skip the following instruction if the value of register VX is not equal to the value of register VY
ANNN Store memory address NNN in register I
BNNN Jump to address NNN + V0
CXNN Set VX to a random number with a mask of NN
DXYN Draw a sprite at position VX, VY with N bytes of sprite data starting at the address stored in I
Set VF to 01 if any set pixels are changed to unset, and 00 otherwise
EX9E Skip the following instruction if the key corresponding to the hex value currently stored in register VX is pressed
EXA1 Skip the following instruction if the key corresponding to the hex value currently stored in register VX is not pressed
FX07 Store the current value of the delay timer in register VX
FX0A Wait for a keypress and store the result in register VX
FX15 Set the delay timer to the value of register VX
FX18 Set the sound timer to the value of register VX
FX1E Add the value stored in register VX to register I
FX29 Set I to the memory address of the sprite data corresponding to the hexadecimal digit stored in register VX
FX33 Store the binary-coded decimal equivalent of the value stored in register VX at addresses I, I+1, and I+2
FX55 Store the values of registers V0 to VX inclusive in memory starting at address I. I is set to I + X + 1 after operation
FX65 Fill registers V0 to VX inclusive with the values stored in memory starting at address I. I is set to I + X + 1 after operation

Opcodes are given in hexadecimal and are 2 bytes (16-bit) wide. Here’s a legend for the non-hexadecimal characters in the opcodes:

NNN A 12-bit address, 0x000-0xFFF
NN An 8-bit value, 0x00-0xFF
X A 4-bit register index, 0x0-0xF
Y A 4-bit register index, 0x0-0xF, usually the second register used by an opcode

The most significant nibble (4-bits) of a 16-bit opcode signifies the operation (or operation group, e.g. the 8xxx instructions). That nibble is followed by one or more arguments, such as addresses to jump to or registers to be used (identified by the registers index).

A few decoding examples (the mnemonics are made up and not standard):

1200 jmp 0x200 Jump to address 0x200
6523 str v5, 0x23 Store value 0x23 in register V5
9340 jneqr v3, v4 Skip the next instruction if V3 and V4 are not equal

Not all bytes in a ROM represent instructions. Bytes can also represent data in a ROM, such as sprites. We are going to ignore this possibility for now.

As we can see from the decoding examples above, we need to be able to operate on 4-bit, 8-bit and 16-bit values. Let's define some extension methods in a new file called Extensions.kt for Int and Byte:

package chip8;

fun Int.toHex() = Integer.toHexString(this)
fun Byte.toHex() = Integer.toHexString(this.toInt())
fun Byte.high() = (this.toInt() and 0xf0) shr 4
fun Byte.low() = this.toInt() and 0xf
fun address(msb: Byte, lsb: Byte) = ((msb.toInt() and 0xf) shl 8) or (lsb.toInt() and 0xff)

The first two extension methods will be attached to Int and Byte. They let us convert those types to a hex string. The Byte#high and Byte#low methods allow us to access the upper and lower nibble of a byte. This will be useful when we want to extract register indices and other nibbles from an opcode. The final function allows us to concatenate two bytes (msb=most significant byte, lsb=least significant byte) of an opcode and get a 12-bit wide address out of it, e.g. for the ANNN opcode.

How to structure the decoding? We shouldn't really care where the two bytes of an opcode came from, so let's create a function that can decode a single opcode given as msb and lsb:

fun decode(msb: Byte, lsb: Byte) {
   // TBD
}

What should this function do? It's main purpose is to "parse" the opcode and perform an action dependent on that opcode. For a disassembler, we want to print the mnemonics to the console or a string. For an interpreter we want to advance the VM state in accordance to the instruction being executed.

The function should thus merely decipher the instruction and its parameters and delegate the actual operation to something else. Let's call that something else a Decoder. For each of our opcodes, the Decoder has a corresponding method that will get invoked by the decode function. Let's make the Decoder a trait (see Traits) (Decoder.kt):

package chip8;

trait Decoder {
    fun before(opCode: Int, address: Int)
    fun unknown(opCode: Int, address: Int)
    fun clear()
    fun ret()
    fun jmp (address: Int)
    fun call(address: Int)
    fun jeq (reg: Int, value: Int)
    fun jneq (reg: Int, value: Int)
    fun jeqr (reg1: Int, reg2: Int)
    fun set (reg: Int, value: Int)
    fun add (reg: Int, value: Int)
    fun setr (reg1: Int, reg2: Int)
    fun or (reg1: Int, reg2: Int)
    fun and (reg1: Int, reg2: Int)
    fun xor (reg1: Int, reg2: Int)
    fun addr (reg1: Int, reg2: Int)
    fun sub (reg1: Int, reg2: Int)
    fun shr (reg1: Int)
    fun subb (reg1: Int, reg2: Int)
    fun shl (reg1: Int)
    fun jneqr (reg1: Int, reg2: Int)
    fun seti (value: Int)
    fun jmpv0 (address: Int)
    fun rand (reg: Int, value: Int)
    fun draw (reg1: Int, reg2: Int, value: Int)
    fun jkey (reg: Int)
    fun jnkey (reg: Int)
    fun getdelay (reg: Int)
    fun waitkey (reg: Int)
    fun setdelay (reg: Int)
    fun setsound (reg: Int)
    fun addi (reg: Int)
    fun spritei (reg: Int)
    fun bcd (reg: Int)
    fun push (reg: Int)
    fun pop (reg: Int)
}

The first two methods are special: before is called before the opcode is decoded, passing in the opcode (as an Int, only the lower 16-bit are used) and the address of the opcode in VmState.ram. unknown is called when the decoder encounters an opcode it does not know.

The rest of the methods map to the opcodes in the table above. The decode function parses the opcode and passes it to the Decoder (main.kt):

fun decode(decoder: Decoder, address:Int, msb: Byte, lsb: Byte) {
    val opCode = (msb.toInt() shl 8 or lsb.toInt().and(0xff)).and(0xffff)
    decoder.before(opCode, address)
    when (msb.high()) {
        0x0 -> {
            when (msb.toInt() shl 8 or lsb.toInt()) {
                0x00e0 -> decoder.clear()
                0x00ee -> decoder.ret()
                else -> decoder.unknown(opCode, address)
            }
        }
        0x1 -> decoder.jmp(address(msb, lsb))
        0x2 -> decoder.call(address(msb, lsb))
        0x3 -> decoder.jeq(msb.low(), lsb.toInt())
        0x4 -> decoder.jneq(msb.low(), lsb.toInt())
        0x5 -> decoder.jeqr(msb.low(), lsb.high())
        0x6 -> decoder.set(msb.low(), lsb.toInt())
        0x7 -> decoder.add(msb.low(), lsb.toInt())
        0x8 -> {
            val reg1 = msb.low()
            val reg2 = lsb.high()
            when(lsb.low()) {
                0x0 -> decoder.setr(reg1, reg2)
                0x1 -> decoder.or(reg1, reg2)
                0x2 -> decoder.and(reg1, reg2)
                0x3 -> decoder.xor(reg1, reg2)
                0x4 -> decoder.addr(reg1, reg2)
                0x5 -> decoder.sub(reg1, reg2)
                0x6 -> decoder.shr(reg1)
                0x7 -> decoder.subb(reg1, reg2)
                0xe -> decoder.shl(reg1)
                else -> decoder.unknown(opCode, address)
            }
        }
        0x9 -> {
            val reg1 = msb.low()
            val reg2 = lsb.high()
            decoder.jneqr(reg1, reg2)
        }
        0xa -> decoder.seti(address(msb, lsb))
        0xb -> decoder.jmpv0(address(msb, lsb))
        0xc -> decoder.rand(msb.low(), lsb.toInt())
        0xd -> decoder.draw(msb.low(), lsb.high(), lsb.low())
        0xe -> {
            when(lsb.toInt() or 0xff) {
                0x9e -> decoder.jkey(msb.low())
                0xa1 -> decoder.jnkey(msb.low())
                else -> decoder.unknown(opCode, address)
            }
        }
        0xf -> {
            val reg = msb.low()
            when(lsb.toInt() or 0xff) {
                0x07 -> decoder.getdelay(reg)
                0x0a -> decoder.waitkey(reg)
                0x15 -> decoder.setdelay(reg)
                0x18 -> decoder.setsound(reg)
                0x1e -> decoder.addi(reg)
                0x29 -> decoder.spritei(reg)
                0x33 -> decoder.bcd(reg)
                0x55 -> decoder.push(reg)
                0x65 -> decoder.pop(reg)
                else -> decoder.unknown(opCode, address)
            }
        }
        else -> decoder.unknown(opCode, address)
    }
}

We can use Kotlin's when expression to perform a switch on the opcode. Notice the use of our extension methods on Int and Byte to easily access the information we want to pass on to the Decoder.

Kotlin note: can this be done better?

A simple Disassembler

Writing our disassembler is easy: we simply the Decoder trait and then loop over all opcodes in the VmState.ram. Here's the Disassembler (Disassembler.kt):

package chip8;

class Disassembler(): Decoder {
    val builder = StringBuilder()
    override fun before(opCode: Int, address: Int) {
        builder.append("addr: 0x${address.toHex()}, op: 0x${opCode.toHex()}, ")
    }
    override fun unknown(opCode: Int, address: Int) {
        builder.append("Unknown opcode addr: 0x${address.toHex()}, op: 0x${opCode.toHex()}")
    }
    override fun clear() {
        builder.line("clear")
    }
    override fun ret() {
        builder.line("ret")
    }
    override fun jmp(address: Int) {
        builder.line("jmp 0x${address.toHex()}")
    }
    override fun call(address: Int) {
        builder.line("call 0x${address.toHex()}")
    }
    override fun jeq(reg: Int, value: Int) {
        builder.line("jeq v${reg.toHex()}, 0x${value.toHex()}")
    }
    override fun jneq(reg: Int, value: Int) {
        builder.line("jneq v${reg.toHex()}, 0x${value.toHex()}")
    }
    override fun jeqr(reg1: Int, reg2: Int) {
        builder.line("jeqr v${reg1.toHex()}, v${reg2.toHex()}")
    }
    override fun set(reg: Int, value: Int) {
        builder.line("set v${reg.toHex()}, 0x${value.toHex()}")
    }
    override fun add(reg: Int, value: Int) {
        builder.line("add v${reg.toHex()}, 0x${value.toHex()}")
    }
    override fun setr(reg1: Int, reg2: Int) {
        builder.line("setr v${reg1.toHex()}, v${reg2.toHex()}")
    }
    override fun or(reg1: Int, reg2: Int) {
        builder.line("or v${reg1.toHex()}, v${reg2.toHex()}")
    }
    override fun and(reg1: Int, reg2: Int) {
        builder.line("and v${reg1.toHex()}, v${reg2.toHex()}")
    }
    override fun xor(reg1: Int, reg2: Int) {
        builder.line("xor v${reg1.toHex()}, v${reg2.toHex()}")
    }
    override fun addr(reg1: Int, reg2: Int) {
        builder.line("addr v${reg1.toHex()}, v${reg2.toHex()}")
    }
    override fun sub(reg1: Int, reg2: Int) {
        builder.line("sub v${reg1.toHex()}, v${reg2.toHex()}")
    }
    override fun shr(reg1: Int) {
        builder.line("shr v${reg1.toHex()}")
    }
    override fun subb(reg1: Int, reg2: Int) {
        builder.line("subb v${reg1.toHex()}, v${reg2.toHex()}")
    }
    override fun shl(reg1: Int) {
        builder.line("shl v${reg1.toHex()}")
    }
    override fun jneqr(reg1: Int, reg2: Int) {
        builder.line("jneqr v${reg1.toHex()}, v${reg2.toHex()}")
    }
    override fun seti(value: Int) {
        builder.line("seti 0x${value.toHex()}")
    }
    override fun jmpv0(address: Int) {
        builder.line("jmpv0 0x${address.toHex()}")
    }
    override fun rand(reg: Int, value: Int) {
        builder.line("rand v${reg.toHex()}, 0x${value.toHex()}")
    }
    override fun draw(reg1: Int, reg2: Int, value: Int) {
        builder.line("draw v${reg1.toHex()}, v${reg2.toHex()}, 0x${value.toHex()}")
    }
    override fun jkey(reg: Int) {
        builder.line("jkey v${reg.toHex()}")
    }
    override fun jnkey(reg: Int) {
        builder.line("jnkey v${reg.toHex()}")
    }
    override fun getdelay(reg: Int) {
        builder.line("getdelay v${reg.toHex()}")
    }
    override fun waitkey(reg: Int) {
        builder.line("waitkey v${reg.toHex()}")
    }
    override fun setdelay(reg: Int) {
        builder.line("setdelay v${reg.toHex()}")
    }
    override fun setsound(reg: Int) {
        builder.line("setsound v${reg.toHex()}")
    }
    override fun addi(reg: Int) {
        builder.line("addi v${reg.toHex()}")
    }
    override fun spritei(reg: Int) {
        builder.line("spritei v${reg.toHex()}")
    }
    override fun bcd(reg: Int) {
        builder.line("bcd v${reg.toHex()}")
    }
    override fun push(reg: Int) {
        builder.line("push v0-v${reg.toHex()}")
    }
    override fun pop(reg: Int) {
        builder.line("pop v0-v${reg.toHex()}")
    }

    override fun toString(): String {
        return builder.toString()
    }
}

We append the textual translation of the opcode to a StringBuilder. Once we decoded all opcodes, we can call code>Disassembler#toString to get the textual representation of the program. Notice the use of Kotlin’s string templating.

I added an extension method for StringBuilder to our Extensions.kt file to make appending new lines easier (Extensions.kt):

fun StringBuilder.line(line: String) {
    this.append(line); this.append("\n")
}

The only thing left to do is to loop through the program’s code and let the Decoder do its thing. But there’s one problem: when we read the ROM into a VmState, we lose the information about the programs size. If we simply iterated over all opcodes in VmState.ram starting at 0x200, we’d output a lot of garbage. Let’s fix this by adding the program size to the VmState and setting it in loadRom:

(VmState.kt)

package chip8;

data class VmState (val ram: ByteArray = ByteArray(4096),
                    val registers: ByteArray = ByteArray(16),
                    var pc: Int = 0x200,
                    var index: Int = 0,
                    var sp: Int = 0,
                    val stack: IntArray = IntArray(16),
                    var delay: Int = 0,
                    var sound: Int = 0,
                    val keys: ByteArray = ByteArray(16),
                    val programSize: Int)

(main.kt)

fun loadRom(file: String): VmState {
    return DataInputStream(BufferedInputStream(FileInputStream(file))).use {
        val rom = it.readBytes()
        val state = VmState(programSize=rom.size)
        System.arraycopy(rom, 0, state.ram, state.pc, rom.size)
        state
    }
}

Note the use of Kotlin’s named arguments feature in line 4 of loadRom.

Now we are ready to write our disassemble function. I decided to put it in Disassembler.kt as a top-level function, much like our loadRom function in main.kt. We could of course just add it to Disassembler as well.

(Disassembler.kt)

fun disassemble(vmState: VmState): String {
    val decoder = Disassembler()
    for(addr in 0x200..(0x200+vmState.programSize - 1) step 2) {
        val msb = vmState.ram[addr]
        val lsb = vmState.ram[addr + 1]
        decode(decoder, addr, msb, lsb)
    }
    return decoder.toString()
}

And for good measure, let’s try our disassembler in main (main.kt):

fun main(args: Array) {
    val vmState = loadRom("roms/maze.rom")
    println(disassemble(vmState))
}

Which gives us the following output for maze.rom:

addr: 0x200, op: 0x6000, set v0, 0x0
addr: 0x202, op: 0x6100, set v1, 0x0
addr: 0x204, op: 0xa222, seti 0x222
addr: 0x206, op: 0xc201, rand v2, 0x1
addr: 0x208, op: 0x3201, jeq v2, 0x1
addr: 0x20a, op: 0xa21e, seti 0x21e
addr: 0x20c, op: 0xd014, draw v0, v1, 0x4
addr: 0x20e, op: 0x7004, add v0, 0x4
addr: 0x210, op: 0x3040, jeq v0, 0x40
addr: 0x212, op: 0x1204, jmp 0x204
addr: 0x214, op: 0x6000, set v0, 0x0
addr: 0x216, op: 0x7104, add v1, 0x4
addr: 0x218, op: 0x3120, jeq v1, 0x20
addr: 0x21a, op: 0x1204, jmp 0x204
addr: 0x21c, op: 0x121c, jmp 0x21c
addr: 0x21e, op: 0x8040, setr v0, v4
addr: 0x220, op: 0x2010, call 0x10
addr: 0x222, op: 0x2040, call 0x40
addr: 0x224, op: 0x8010, setr v0, v1

Thankfully we also have the source code of maze.rom, so we can compare the output of the disassembler to it:

    LD  V0, 0
    LD  V1, 0

LOOP:
    LD  I,  LEFT    ; We draw a left line by default, as the random number
                    ; is 0 or 1. If we suppose that it will be 1, we keep
                    ; drawing the left line. If it is 0, we change register
                    ; I to draw a right line.

    RND V2, 1       ; Load in V2 a 0...1 random number

    SE  V2, 1       ; It is 1 ? If yes, I still refers to the left line
                    ; bitmap.

    LD  I,  RIGHT   ; If not, we change I to make it refer the right line
                    ; bitmap.

    DRW V0, V1, 4   ; And we draw the bitmap at V0, V1.

    ADD V0, 4       ; The next bitmap is 4 pixels right. So we update
                    ; V0 to do so.

    SE  V0, 64      ; If V0==64, we finished drawing a complete line, so we
                    ; skip the jump to LOOP, as we have to update V1 too.

    JP  LOOP        ; We did not draw a complete line ? So we continue !

    LD  V0, 0       ; The first bitmap of each line is located 0, V1.

    ADD V1, 4       ; We update V1. The next line is located 4 pixels doan.

    SE  V1, 32      ; Have we drawn all the lines ? If yes, V1==32.
    JP  LOOP        ; No ? So we continue !

FIN:    JP FIN      ; Infinite loop...

RIGHT:              ; 4*4 bitmap of the left line

    DB $1.......
    DB $.1......
    DB $..1.....
    DB $...1....

LEFT:               ; 4*4 bitmap of the right line
                    ; And YES, it is like that...
    DB $..1.....
    DB $.1......
    DB $1.......
    DB $...1....

That seems to match up pretty nicely, except for the opcodes starting at address 0x21e. In the original source, those bytes are actually data (sprites to be exact). Our disassembler doesn’t know that those bytes aren’t instructions, so it tries its best to decode them.

This is a problem we’ll have to resolve next time. It turns out that when writing an interpreter, we’ll never really encounter those bytes as instructions. Due to jumps in the code, the interpreter will never reach those bytes. Pretty simple fix.

However, if we wanted to write an ahead-of-time compiler that translates the Chip8 instructions to Java bytecode or machine code, this is going to be a problem. For that we’ll need to figure out the basic blocks of our program. And even then, we can’t be 100% sure, as Chip8 has non-absolute jump instructions based on register contents.

Up Next

We can now load a ROM and disassemble it (naively). Next time we are going to refactor things a little to be more idiomatic.

Happy coding!

Previously

Let’s write a Chip8 emulator in Kotlin [Part 0: Motivation & Setup]

Following Along

  1. Install the required tools (JDK, IDEA, IDEA Kotlin plugin, Git)
  2. Clone the repo: git clone https://github.com/badlogic/chip8.git
  3. Checkout the tag for the article you want to work with: git checkout part1
  4. Import the project into IDEA (Open Project, select build.gradle file, select "Use customizable gradle wrapper"

Let’s write a Chip8 emulator in Kotlin [Part 0: Motivation & Setup]

Disclaimer

This series is a means for me to learn Kotlin. As such i might misuse some features of Kotlin, not follow best practices or simply to silly things. Please always check the comment section for feedback from more knowledgable people. I’ll also have follow up articles where i refactor the code to be more idiomatic. So, make sure to check back.

Why?

I recently started looking into alternative JVM languages. After a short interaction with Scala, i looked into JetBrains’ Kotlin. When learning a new language/platform, i usually write a simple application that allows me to iteratively explore new concepts and features. For my adventure in Kotlin i chose to write a Chip8 emulator.

My goals with this little series are as follows:

  • Learn a bit of Kotlin
  • Document my journey
  • Get feddback from Kotlin users
  • Show how to write a simple emulator

My non-goals are:

  • Teach anyone Kotlin, use the official docs if you want to learn it all
  • Write the most efficient and precise Chip8 emulator

Let’s get going.

Setting up a Kotlin project

As stated earlier, i’m not going to try and teach anyone Kotlin given my knowledge level. Instead i’ll comment on the tooling surrounding Kotlin. What i present may not be best practices but it’s what has worked for me.

The project i’m going to set up has to fulfil these requirements:

  1. Build management and packaging
  2. Dependency management
  3. IDE integration
  4. Version control

Requirements 1 and 2 can be achieved by choosing one or combining multiple of the following tools: Ant, Ivy, Maven, Gradle, SBT and so on. I’m going to use Gradle as there exists a first class Gradle plugin for Kotlin. It’s also quite a bit less verbose than Maven and hence suitable for a textual series like this.

Being a brain-child of JetBrains, Kotlin has first class support in Intellij IDEA, so that’s what i’m going to use. Quite a change for an Eclipse guy like me.

On the version control front i’m going to go with Git, hosting a repository on Github.

Given these choices, i need to install the following:

  • Latest JDK (make sure the bin/ folder is in your PATH)
  • A Git client (TortoiseGit on Windows, on Linux use your package manager, on Mac OS X use homebrew to get the latest and greatest)
  • Intellij IDEA 13 (community edition will do)
  • Gradle (make sure the bin/ folder is in your path)

Project Structure, Build and Dependency Management

Let’s start by setting up a few folders and files for our project. We’ll add a build.gradle file describing our Gradle build first:

buildscript {
  repositories {
    mavenCentral()
  }
  dependencies {
    classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:0.8.11'
  }
}

apply plugin: "kotlin"
apply plugin: "java"

repositories {
  mavenCentral()
}

dependencies {
  compile 'org.jetbrains.kotlin:kotlin-stdlib:0.8.11'
}

project.ext.mainClassName = "chip8.Chip8Package"

task run(dependsOn: classes, type: JavaExec) {
    main = project.mainClassName
    classpath = sourceSets.main.runtimeClasspath
    standardInput = System.in
    ignoreExitValue = true
}

task dist(type: Jar) {
    from files(sourceSets.main.output.classesDir)
    from files(sourceSets.main.output.resourcesDir)
    from {configurations.compile.collect {zipTree(it)}}
 
    manifest {
        attributes 'Main-Class': project.mainClassName
    }
}

task wrapper(type: Wrapper) {
    gradleVersion = '2.1'
}

The buildscript block is required to pull in the Kotlin Gradle plugin (line 6). Line 10 and 11 specify that this project uses both the Kotlin and Java plugin, allowing us to mix and match the two languages if required. Next we define the repositories from where dependencies are fetched. For now, the only required dependency is the Kotlin standard library (line 18). Finally i added two tasks (run, dist) to run the project and package it into a JAR for distribution. Note line 21 where i define the main class. That class name is used by the run and dist to execute our app and package our app as a runnable JAR respectively.

The wrapper task pulls the Gradle wrapper files into our project structure. This will allow other people to work with the project without installing Gradle themselves.

The last missing bit is a source folder and main entry point. Per convention, the Kotlin source files are placed under src/main/kotlin, Java source files are put into src/main/java. Tests go into src/test/kotlin and src/test/java respectively. I put a very simple Hello-World style Kotlin app in src/main/kotlin/chip8/main.kt:

package chip8;

fun main(args: Array) {
   println("Hello World")
}

Where is the Chip8Package class we defined as the main class in the build.gradle file? Turns out that Kotlin will put all top-level functions of a package into a synthetic class called <Packagename>Package. We therefor have to specify that synthetic class for the JVM to run our main function.

Before moving on, we’ll invoke the wrapper task to pull the Gradle wrapper into our project:

chip8$ gradle wrapper

The wrapper is composed of two script files (gradlew for *nix and gradlew.bat for Windows) and a folder called gradle/ which contains a tiny JAR and a properties file. From now on, we’ll invoke the script files (gradlew, gradlew.bat) in our project directory.

Running the app on the command line is as simple as calling the run task:

chip8$ ./gradlew run
Downloading https://services.gradle.org/distributions/gradle-2.1-bin.zip
.............................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................
Unzipping /Users/badlogic/.gradle/wrapper/dists/gradle-2.1-bin/2pk0g2l49n2sbne636fhtlet6a/gradle-2.1-bin.zip to /Users/badlogic/.gradle/wrapper/dists/gradle-2.1-bin/2pk0g2l49n2sbne636fhtlet6a
Set executable permissions for: /Users/badlogic/.gradle/wrapper/dists/gradle-2.1-bin/2pk0g2l49n2sbne636fhtlet6a/gradle-2.1/bin/gradle
:compileKotlin
warning:Annotations path entry points to a non-existent location: 
:compileJava UP-TO-DATE
:processResources UP-TO-DATE
:classes UP-TO-DATE
:run
Hello World

BUILD SUCCESSFUL

Total time: 59.466 secs

Note that we now use the gradlew script instead of our local Gradle installation. The first time the wrapper is invoked, it downloads the Gradle version we specified in the wrapper task and installs it. This may take some time but won’t be repeated on subsequent invocations.

We can also package our app as a runnable JAR via the dist task and run that JAR:

chip8$ ./gradlew dist
chip8$ java -jar build/libs/chip8.jar

The project layout now looks like this:

chip8/
   build.gradle   
   src/
      main/
         kotlin/
            main.kt
   gradlew
   gradlew.bat
   gradle/
      wrapper/
         gradle-wrapper.jar
         gradle-wrapper.properties

IDE Integration

Before we can import our project into Intellij IDEA, we need to install the IDEA Gradle plugin. Fire up IDEA, on the welcome screen go to Configure->Plugins, click on Install JetBrains plugin, search for Kotlin and install the plugin.

We can now import the project by clicking on Open Project on the welcome screen, navigating to the project folder and select the build.gradle file. IDEA will ask us some specifics about our Gradle project. Make sure to select Use customizable gradle wrapper, so IDEA uses our wrapper properly.

Once loaded, we can run our main function. The simplest way to do this is to open up the main.kt file in IDEA, right click and select Run 'chip8.Chip8Package':

This will also create a configuration for us which we can use to launch and debug the app later on. Speaking of debugging, it works as you expect. Simply create a breakpoint in a source file in the line number margin on the left and start your app in debug mode:

Version Control

Time to push our master piece to Github. I created a Github repository called chip8 which you can follow along. Since we already have a folder, we can initialize the repository with a few magic incantations on the command line:

chip8$ git init
chip8$ git remote add origin https://github.com/badlogic/chip8.git

We should also create a .gitignore file so we don’t commit the build directory, IDEA project files and so forth:

.DS_STORE
.idea/
.gradle/
build/
target/
*.iml

Let’s add our initial set of files, commit and push them to the remote repository:

chip8$ git add
chip8$ git status
On branch master

Initial commit

Changes to be committed:
  (use "git rm --cached ..." to unstage)

	new file:   .gitignore
	new file:   build.gradle
	new file:   gradle/wrapper/gradle-wrapper.jar
	new file:   gradle/wrapper/gradle-wrapper.properties
	new file:   gradlew
	new file:   gradlew.bat
	new file:   src/main/kotlin/chip8/main.kt
chip8$ git commit -m "initial commit"
chip8$ git push -u origin master

Let’s also tag this state:

chip8$ git tag -a part0 -m "part0"
chip8$ git push --tags

And we are done!

Up Next

You can now simply clone the repository and follow along the rest of the series, provided you installed all the tools we talked about earlier.

In the next article we’ll write a simple Chip8 ROM disassembler.

Following Along

  1. Install the required tools (JDK, IDEA, IDEA Kotlin plugin, Git)
  2. Clone the repo: git clone https://github.com/badlogic/chip8.git
  3. Checkout the tag for the article you want to work with: git checkout part0
  4. Import the project into IDEA (Open Project, select build.gradle file, select "Use customizable gradle wrapper"

Happy coding!

libGDX wins Duke’s Choice Award

Damn right. The Duke’s Choice Award is an anual thing given out by a jury of judges as well as the community to projects from the Java realm based on various criteria. It’s one of the biggest honors you can get in the Java world. Here’s the Java Magazine article on the winners, including my ugly face, Robotality’s Halfway and Interrupt’s Delver (which you should both buy and play the hell out of):

The story behind that photo is actually quite funny. Oracle send a photographer from the Netherlands to my place in Graz, via car. He took about 500 images. I felt very special that day…

Thanks to all the contributors, this award goes out to all of you (you can put it on your resume :D).