Welcome back! In our previous lesson, we examined the CHIP-8's built-in font set, understanding how characters are represented as binary sprites and stored in memory. We concluded by noting that an instruction like FX29 is used to access this font data. This naturally raises a fundamental question: how does the CHIP-8 processor understand what FX29—or any other command—means?
This lesson will answer that by diving into the heart of the CHIP-8's design: its instruction format. We will explore how every operation the virtual machine can perform is encoded into a 2-byte value called an opcode. Our goal is to describe this format and, crucially, understand the process of extracting meaningful data from it. Your background in computer architecture and experience with low-level systems will be a great asset here, as we'll be working directly with binary data representation.
Opcodes: The Language of the CPU
At its core, a CPU executes a sequence of instructions. An opcode (short for operational code) is the numeric representation of a single instruction that the CPU can execute directly. The collection of all possible opcodes for a given CPU is its instruction set. The CHIP-8 has a very small and simple instruction set of only 35 opcodes, which makes it an excellent case study.
Every CHIP-8 instruction is exactly 2 bytes (16 bits) long. These opcodes are stored in memory in big-endian format. This means that for a 2-byte opcode like 0x1234, the most significant byte (0x12) is stored at a memory address m, and the least significant byte (0x34) is stored at the next address, m + 1. When our emulator fetches an instruction, it will need to read these two bytes and combine them to form the full 16-bit opcode.
The Anatomy of an Instruction
A 16-bit opcode is not just a single number; it's a composite value where different groups of bits, or fields, carry different meanings. These fields tell the CPU what operation to perform and what data (operands) to use.
The image below illustrates how a 16-bit CHIP-8 opcode is broken down into its common fields. A 4-bit group is often called a nibble, which corresponds to one hexadecimal digit.

As you can see, the same 16 bits can be interpreted in several ways depending on the specific instruction. The most common fields are:
- INSTRUCTION (or Type): The first nibble (bits 12-15). This is the most important field, as it usually defines the broad category of the instruction. For example, all opcodes beginning with
8involve two-register arithmetic or logical operations. - X: The second nibble (bits 8-11). It is almost always used to specify one of the 16 general-purpose registers, from
V0toVF. - Y: The third nibble (bits 4-7). Like
X, this field is often used to specify a second register operand,VY. - N: The fourth nibble (bits 0-3). A 4-bit immediate value.
- NN (or KK): The second byte (bits 0-7). An 8-bit immediate value.
- NNN: The last three nibbles (bits 0-11). A 12-bit immediate value, typically used for memory addresses.
Extracting Fields with Bitwise Operations
To decode an opcode, we must isolate these fields. This is accomplished using standard bitwise operations—a concept you'll be very familiar with from your hardware and systems programming experience. The primary tools are the bitwise AND operator (&) for masking and bit-shift operators (>>, <<) for moving bits into position.
The following video provides an excellent visual demonstration of this process. The presenter uses a digital whiteboard to explain how to use masks and shifts to extract the various parts of an opcode.
Building and understanding a Virtual Machine in 2hrs (CHIP-8 Emulator)
This segment from the video "Building and understanding a Virtual Machine in 2hrs" provides a clear, step-by-step walkthrough of how CHIP-8 opcodes are structured and how to parse them using bitwise logic.
Watch from the introduction to the instruction format, where the presenter defines the different fields like addr (NNN), x, y, and n. Then, continue through the detailed explanation of binary operations, where he demonstrates how bitwise AND and shifting are used to isolate specific values from a sample 16-bit instruction. Pay close attention to the hexadecimal masks he uses (e.g., 0x0F00). Note that he says "left shift" but the operation he performs to isolate the value is a right shift (>>).
As the video demonstrates, by applying a specific hexadecimal mask with AND, we can zero out all the bits we don't care about, leaving only the field of interest. Then, if necessary, we can right-shift the result to align it as a standard integer value.
Now, let's formalize the definitions of these fields. The following reading provides a concise summary.
Guide to making a CHIP-8 emulator - Tvil
This guide by Tobias V. Langhoff offers a clear breakdown of the instruction format.
Please read the four paragraphs that follow the heading Decode. This section formally defines the X, Y, N, NN, and NNN fields and explains their purpose. It also makes an excellent point about extracting these values before the main decoding logic to improve code clarity and prevent errors.
A Practical Example
Let's combine these concepts into a practical example of how you would parse an opcode in JavaScript. Imagine our emulator has fetched the two bytes 0x85 and 0xA4 from memory and combined them into the 16-bit opcode 0x85A4. This is an instruction of the 8XY4 type, which means "Add the value of register VY to register VX, and set VF to 1 if there is a carry."
Here is how we would extract the X and Y register indices from this opcode:
-
Isolate X (the
5):- Mask:
0x85A4 & 0x0F00results in0x0500. - Shift:
0x0500 >> 8results in0x0005, or simply5. So,VXisV5.
- Mask:
-
Isolate Y (the
A):- Mask:
0x85A4 & 0x00F0results in0x00A0. - Shift:
0x00A0 >> 4results in0x000A, or simply10. So,VYisVA.
- Mask:
This process can be generalized to extract any of the possible fields from any opcode. The resource below shows this implemented in Python, but the logic is identical for JavaScript.
[PDF] BUILDING A CHIP-8 VIRTUAL MACHINE - No Starch Press
This chapter from "Computer Science from Scratch" gives a clear, commented code example of the decoding process.
Find the step() method definition and read the code block and the paragraph that follows it, from the start down to the end of the explanatory text. This snippet shows exactly how two bytes are fetched from memory and then broken down into four individual nibbles using bitwise masking and shifting.
Translating that Python logic directly into JavaScript, your decoding preamble inside your emulator's CPU cycle function would look something like this:
// Assume 'opcode' is a 16-bit integer representing the full instruction
// For example: const opcode = 0x85A4;
// Isolate the 4 nibbles
const n1 = (opcode & 0xF000) >> 12; // The first nibble (e.g., 8)
const n2 = (opcode & 0x0F00) >> 8; // The second nibble (e.g., 5)
const n3 = (opcode & 0x00F0) >> 4; // The third nibble (e.g., 10 or 'A')
const n4 = opcode & 0x000F; // The fourth nibble (e.g., 4)
// Extract the common fields using the nibbles or combined masks
const x = n2; // Register identifier
const y = n3; // Register identifier
const n = n4; // A 4-bit immediate value
const nn = opcode & 0x00FF; // An 8-bit immediate value (n3 and n4)
const nnn = opcode & 0x0FFF; // A 12-bit address (n2, n3, and n4)
With the opcode parsed into these distinct parts, the emulator can then use a switch statement or a lookup table, typically keyed on the first nibble (n1), to dispatch the instruction to the correct handling function.
Conclusion
In this lesson, we have dissected the CHIP-8 instruction format. We now have a complete theoretical model for how the virtual machine's commands are structured and processed.
Here are the essential takeaways:
- All CHIP-8 instructions are 2-byte (16-bit) opcodes, stored in big-endian format.
- An opcode is composed of several fields (
X,Y,N,NN,NNN) that encode the operation and its operands. - The most significant nibble (4 bits) typically identifies the instruction's category.
- We use bitwise masking (
&) and shifting (>>) to extract these fields from the 16-bit integer. This is the fundamental process of decoding.
We are now nearing the end of our architectural tour. In the next lesson, we will cover the final components of the CHIP-8 system: the display, the keypad, and the timers. Once we have covered those, we will have a complete blueprint of the machine and will be ready to begin implementing the core of our emulator.