Conditional Opcodes — Skip Instructions

CHIP-8 has no if statement. Instead, it has four "skip" opcodes: they advance PC by an extra 2 bytes when a condition is true — jumping over the next instruction.

Combined with JP, this creates an if/else pattern.

How skip works

A normal fetch advances PC by 2. A skip adds another 2, so:

normal:  fetches current instruction, moves to the next
skip:    fetches current instruction, jumps over the next

The four skip opcodes

3XNN — SE Vx, byte (skip if Vx == NN)

#![allow(unused)]
fn main() {
0x3 => {
    let x  = ((opcode & 0x0F00) >> 8) as usize;
    let nn = (opcode & 0x00FF) as u8;
    if self.v[x] == nn { self.pc += 2; }
}
}

4XNN — SNE Vx, byte (skip if Vx != NN)

The inverse of 3XNN.

5XY0 — SE Vx, Vy (skip if Vx == Vy)

Compares two registers instead of a register and an immediate value:

#![allow(unused)]
fn main() {
0x5 => {
    let x = ((opcode & 0x0F00) >> 8) as usize;
    let y = ((opcode & 0x00F0) >> 4) as usize;
    if self.v[x] == self.v[y] { self.pc += 2; }
}
}

9XY0 — SNE Vx, Vy (skip if Vx != Vy)

The inverse of 5XY0.

Typical usage

SE  V0, 16    ; if digit counter == 16, skip next
JP  0x206     ; else loop back to the start
; (halt)      ; execution falls here when V0 reaches 16

This is exactly how the test program in main.rs stops the loop:

#![allow(unused)]
fn main() {
0x30, 0x10,  // SE V0, 16  — if digit == 16, skip JP
0x12, 0x06,  // JP 0x206   — else loop
0x12, 0x12,  // JP 0x212   — halt (infinite loop)
}

Summary

OpcodeCondition
3XNNVx == NN
4XNNVx != NN
5XY0Vx == Vy
9XY0Vx != Vy

Rust concepts used here: The extraction pattern uses Bitwise Operations and Type Casting. These opcodes do not introduce new Rust concepts — they reinforce the established masking pattern. See JP — The First Opcode and CALL and RET for the other control flow opcodes.