Writing a Chip 8 Emulator – DRAW command (Part 3)

Previously, I wrote about the Chip 8’s basic execution structure, as well as how to decode and understand its instructions. While many of the commands are used to perform logical operations or fetch data from memory, I think that commands that perform input or output routines are equally important. After all, how can you interact with the emulator if you have no way of seeing what it is doing? In this post, I wanted to talk about one of the most important instructions in the Chip 8 arsenal: the DRAW command.

The Chip 8 Screen

First, a little background. The Chip 8 defines a single screen that is 64 pixels wide by 32 pixels tall. It’s a small screen, but it is a nice size to work with if you are writing an emulator for the first time.

Continuing on with simplicity, the Chip 8 is only capable of drawing pixels in a single color: white. Limited, yes, but easy to work with. If you like, you can have your Chip 8 emulator print the pixel out in any color at all. You just need to know that you can’t write a Chip 8 program that controls the color of the pixels written to the screen.

Writing Pixels with XOR

The Chip 8 has a really simple method of drawing a pixel to the screen. You can only control whether you want to turn a pixel on 1 or turn it off 0. When you turn on a pixel, it appears in the single glorious color that the Chip 8 has.

But writing to the Chip 8 screen isn’t just a simple matter of turning a pixel on and off. All write operations are governed by an XOR (exclusive OR) routine. The reason why the Chip 8 uses an XOR routine is because it makes certain graphics operations easier. However, it is a somewhat tricky concept if you are new to it. Bear with me while I draw out the truth table for an XOR:

0 xor 0 = 0
0 xor 1 = 1
1 xor 0 = 1
1 xor 1 = 0

To break this down, there are four different operations you can perform on the pixels. The first two work as you would expect them to:

  • When you turn off 0 a pixel that is already turned off 0, the pixel remains off 0.
  • When you turn on a pixel 1 that is turned off 0, the pixel turns on 1.

With me so far? Here’s where things get a little trickier:

  • When you turn off 0 a pixel that is turned on 1 the pixel will remain on 1.
  • When you turn on 1 a pixel that is already on 1, you end up turning it off 0.

If the draw operation turns off a pixel that was already on, it will store the value 1 in register VF. This is a really important detail to remember, since it allows you to perform sprite hit detection. Which brings us to the next item of business.

Sprites

Yes, the Chip 8 actually had a concept of a sprite. To those who don’t know, a sprite is a distinct graphical object. Usually, in the realm of computer games, a sprite would be something like a player’s character on the screen.

Everything is a sprite to the Chip 8. This means that you don’t have to turn a pixel off or on individually; instead you control groups of pixels as specified by the sprite. Each sprite can be between 1 and 15 bytes long. The bit patterns within the bytes that you specify correspond to the pixels you want turned on or off. As you would expect, 1 turns on a pixel, and 0 turns off the pixel (subject of course to the above XOR rules). Here’s a simple example of a 7 byte sprite:

         bit 7 6 5 4 3 2 1 0
-------+--------------------
byte 1 |     0 1 1 1 1 1 0 0 
byte 2 |     0 1 0 0 0 0 0 0 
byte 3 |     0 1 0 0 0 0 0 0  
byte 4 |     0 1 1 1 1 1 0 0   
byte 5 |     0 1 0 0 0 0 0 0 
byte 6 |     0 1 0 0 0 0 0 0 
byte 7 |     0 1 1 1 1 1 0 0

Do you see the pattern? The sprite represents a capital E character. In hex, these bytes would be the following values:

         hex
-------+----
byte 1 |  7C
byte 2 |  40
byte 3 |  40
byte 4 |  7C
byte 5 |  40
byte 6 |  40
byte 7 |  7C

These 7 bytes would have to be placed somewhere in the Chip 8’s memory in order to write this sprite pattern to the screen. They also have to be placed in the order that you see them.

Using the Index Register

The index register is used to specify where in memory the sprite resides. This means that before you issue the DRAW command using the Chip 8 instruction set, you must first load the memory location of the sprite into the index register. Continuing our example above, say you stored the 7 byte sprite starting at location 0x300. You would need to first load the index register with that location. In machine code this is A300. The DRAW command would then read byte 1 from 0x300, byte 2 from 0x301, and so on.

The DRAW Command

Okay, with all the background out of the way, we can finally examine the core DRAW instruction. The instruction takes on the form Dxyn. If you recall my earlier post, the D remains the same for all draw commands. The other options are:

  1. x – this specifies the register that stores the X coordinate where you want to draw the sprite. Valid X coordinates range from 0 to 63. Values larger than 63 will cause the sprite to wrap horizontally across the screen.
  2. y – this specifies the register that stores the Y coordinate where you want to draw the sprite. Valid X coordinate range from 0 to 31. Values larger than 31 will cause the sprite to wrap vertically across the screen.
  3. n – this specifies how many bytes the sprite is. Valid number of bytes range from 0 to 15.

Let’s go through a simple program that draws out the capital E at the location 10, 5 (X, Y). Here are the steps you would need to take:

  1. Put the sprite bytes somewhere in memory (say location 0x300).
  2. Load the index with the start of the sprite bytes.
  3. Load a register with the value 10 (say register 0).
  4. Load a register with the value 5 (say register 1).
  5. Issue the DRAW command (becomes D017).

The complete assembly listing for this program would be the following (note that I am using my Chip 8 Assembler to write and compile the assembly code for this example):

# Writes the letter E to the location (10, 5)
start   LOADI  sprite   Load the sprite location into index
        LOAD   r0,$A    Load 10 into register 0
        LOAD   r1,$5    Load 5 into register 1
        DRAW   r0,r1,$7 Draw 7-byte sprite at r0, r1
end     JUMP   end      Loop infinitely
# Data for the program
sprite  FCB    $7C      Capital letter E
        FCB    $40
        FCB    $40
        FCB    $7C
        FCB    $40
        FCB    $40
        FCB    $7C

The assembled statements (with their memory locations and the program listing) are the following:

-- Assembled Statements --
0x0200 A20A  start LOADI   sprite  # Load the sprite location into index
0x0202 600A         LOAD    r0,$A  # Load 10 into register 0
0x0204 6105         LOAD    r1,$5  # Load 5 into register 1
0x0206 D017         DRAW r0,r1,$7  # Draw 7-byte sprite at r0, r1
0x0208 1208    end  JUMP      end  # Loop infinitely
0x020A 007C sprite   FCB      $7C  # Capital letter E
0x020B 0040          FCB      $40  #
0x020C 0040          FCB      $40  #
0x020D 007C          FCB      $7C  #
0x020E 0040          FCB      $40  #
0x020F 0040          FCB      $40  #
0x0210 007C          FCB      $7C  #

For those of you who don’t use my assembler, the second column in the output are the assembled statements. You can punch those into a hex editor manually to create the Chip 8 program. When we run the program, we get the following:

e_program_output

Wrapping Up

It is extremely easy to draw sprites on the screen. All you need to do is load the index register with the memory location of your data, set the X and Y coordinates into two registers, and then issue the DRAW command. In a future post, I’ll look at how you can make writing text in a Chip 8 program easier with the built in font file and the load sprite command (Fs29).

Writing a Chip 8 Emulator (Part 2)

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 following instruction:

1234

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:

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

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 apply a bit mask with a logical AND operation, and shift the bits.

To break this down into a few steps, first 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 like so:

1111 0000

That number in hex 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 – the hex value of $2, 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.