Writing a Chip 8 Emulator (Part 2)

Posted on Thu 17 July 2014 in Emulation

In a previous post, I wrote about the basic CPU structure needed to emulate a Chip 8. I also wrote about memory access, and how to define bytes and words. In this post, I will talk about how to interpret Chip 8 instructions, as well as how to set up a main emulator loop.


The Execution Cycle

Every computer executes a simple loop over and over again while it is powered up. This loop consists of the following steps:

  • Fetch
  • Decode
  • Execute

During the fetch portion of the loop, the CPU will grab the contents of memory where the Program Counter is currently pointing. This memory content indicates the instruction that the CPU should execute. For example, let’s say that the program counter points at memory location $0200, and that the data $1100 is stored there (technically, $11 is stored in location $0200, and $00 is stored in location $0201). The fetch portion of the cycle will load the CPU with the instruction $1100 (more on what this instruction actually does a little later).

The decode portion of the loop translates the instruction into a form that the CPU understands. Each instruction has a specific number of operands that are associated with it. These are essentially parameters that describe what registers or memory locations the instruction should actually be executed on. In the case of the Chip 8, the operands are all part of the 16-bit word that is read from the Program Counter. For example, with the instruction $1100, the $1 indicates that the instruction is a JUMP instruction, while the 100 is the operand that relates to the address that the CPU needs to jump to.

The execute portion of the loop actually executes the instruction on the operands that were previously decoded. In the case of the decoded JUMP instruction $1100, the CPU will advance the program counter to the memory location $100.

The computer will continue performing this loop over and over again until the power is turned off. Now that we understand what we need to do in the main loop, let’s look at how we decode the different instructions.


Anatomy of a Chip 8 Instruction

Chip 8 instructions are actually really nice to work with. Each instruction that the CPU can execute is exactly two bytes long, making them a 16-bit word in length. All of the data needed to execute the instruction is stored within this 16-bit word. Let’s take a look at a simple instruction and dissect how the operation works.

The Jump Instruction

The Chip 8 JUMP instruction tells the CPU to move the Program Counter to the address specified. The JUMP instruction is encoded as follows:

1nnn

The n characters are used to denote 4-bit numbers. When combined, the numbers form a 12-bit address that the CPU should jump to. For example, if the CPU encountered the instruction $1234 then the CPU would set the program counter to $234. During the next loop of the CPU, the next instruction would be fetched from location $234.

The Load Operation

Let’s look at another example. The LOAD instruction tells the CPU to load a specific number into a register. The LOAD instruction is encoded as follows:

6snn

The s character is used to denote a 4-bit value that specifies a register to store the number in. The n characters denote 4-bit numbers that, when combined, indicate the 8-bit value that should be loaded into that register. For example, if the CPU encountered the instruction $6248 the CPU would load the value $48 into register 2.


Chip 8 Operands

We can start to see patterns in the way a Chip 8 instruction is encoded. The first hex digit of the instruction usually provides a hint at what major operation is about to occur. The next three hex digits encode numeric information, or the registers that the operations work on. Here is a mostly complete set of Chip 8 instructions:

  • 00E0 - Clear the screen
  • 00EE - Return from subroutine
  • 1nnn - Jump to address nnn
  • 2nnn - Call routine at address nnn
  • 3snn - Skip next instruction if register s value equals nn
  • 4snn - Do not skip next instruction if register s value equals nn
  • 5st0 - Skip if register s value equals register t value
  • 6snn - Load register s with value nn
  • 7snn - Add value nn to register s
  • 8st0 - Move value from register s to register t
  • 8st1 - Perform logical OR on register s and t and store in t
  • 8st2 - Perform logical AND on register s and t and store in t
  • 8st3 - Perform logical XOR on register s and t and store in t
  • 8st4 - Add s to t and store in s - register F set on carry
  • 8st5 - Subtract s from t and store in s - register F set on !borrow
  • 8s06 - Shift bits in register s 1 bit to the right - bit 0 shifts to register F
  • 8s0E - Shift bits in register s 1 bit to the left - bit 7 shifts to register F
  • 9st0 - Skip next instruction if register s not equal register t
  • Annn - Load index with value nnn
  • Bnnn - Jump to address nnn + index
  • Ctnn - Generate random number between 0 and nn and store in t
  • Dstn - Draw n byte sprite at x location reg s, y location reg t
  • Ft07 - Move delay timer value into register t
  • Ft0A - Wait for keypress and store in register t
  • Fs15 - Load delay timer with value in register s
  • Fs18 - Load sound timer with value in register s
  • Fs1E - Add value in register s to index
  • Fs29 - Load index with sprite from register s
  • Fs33 - Store the binary coded decimal value of register s at index
  • Fs55 - Store the values of register s registers at index
  • Fs65 - Read back the stored values at index into registers

Bit Shifting and Bit Masks

So, when writing an emulator, how do you separate out the various operands? The simple way is through bit masks and bit shifting. A bit mask is a binary pattern, usually used to turn certain bits on or off, or used to determine if a particular bit pattern is activated. Bit shifting is where the bits in a pattern are moved left or right by a certain number of spaces. Let's work through a simple mask and shift example.

Let's say that we had the hex number $23. In binary, this would be 0010 0011. The 0010 corresponds to the 2, while the 0011 corresponds to the 3. Now let's say you want to get just the hex value of the first digit. What we do is 1) apply a bit mask with a logical AND operation, and 2) shift the bits.

For the first step, what we want to do is isolate the high 4-bits in the pattern, and blank everything else out. We do that by applying a bit mask that has all ones in the high 4-bits. In binary, this would be1111 0000 - hex it is $F0. Now that we know the mask value we need to apply, the next thing we do is apply a logical AND operation to $23 and $F0. The way an AND works is if both digits are a 1, then a 1 is the result, otherwise, the value is a 0. When we apply the AND:

0010 0011
1111 0000
--------- &
0010 0000

The result we are left with is 0010 0000, which is the hex value of $20. Now that we have blanked everything else out, we perform the second step, which is the logical shift. We want to move all of the bits of the result 4 places to the left. In a language like C, this is done with the >> operator.

0010 0000
--------- >> 4
0000 0010

This leaves us with 0000 0010 which is the hex value of $02, which is exactly what we wanted!

Shifting and Masking Chip 8 Instructions

Let's take a look at the Chip 8 instruction to move a value of one register into another. This corresponds to the instruction 8st0. This says to move the value in register s into the register t. An example of this would be $8620, which would make the Chip 8 CPU move the value in register 6 into register 2.

The first thing we need is the value for s. Using a bit mask, we want the following:

1000 0110 0010 0000
0000 1111 0000 0000
------------------- &
0000 0110 0000 0000

Once again, the magic hex value is $F for the 4-bit pattern 1111. The same operation in hex gives us:

$ 8 6 2 0
$ 0 F 0 0
  ------- &
$ 0 6 0 0

This gives us the result $0600. Now we need to shift the bits 8 places to the left this time:

0000 0110 0000 0000
------------------- >> 8
0000 0000 0000 0110

The same operation in hex gives us:

$ 0 6 0 0
  ------- >> 8
$ 0 0 0 6

So now we know that the s value should be $6! We do the same thing for the t register, except this time we only need to perform a shift of 4 places to the left. The hex operations are as follows:

$ 8 6 2 0
$ 0 0 F 0 
  ------- &
$ 0 0 2 0
  ------- >> 4
$ 0 0 0 2

This tells us that the t value should be $2!

The key points to take away here are:

  • Groups of 4-bits correspond to a single hex number. For example, 1001 corresponds to the hex digit $9.
  • Bit masks allow you to figure out specific bit patterns in a larger bit sequence.
  • When performing bit shifting hex values, always shift in multiples of 4. So if you want to get the $6 in $8620, you need to shift 2 hex digits to the left, which is 8 binary bits to the left (2 x 4).
  • If you are decoding the JUMP instruction, which is 1nnn, you don’t need to shift anything! Just apply the bit mask of $0FFF and you have the address you need to jump to.

Conclusion

In this post, we looked at the execution loop, and examined how Chip 8 instructions are encoded. We also looked at bit shifting and bit masks, and how those two simple tools can be used during the decoding phase. In a future post, I’ll look at some of the Chip 8 instructions in more detail, as well as how to code the execution loop in a language like C.