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
| Opcode | Condition |
|---|---|
3XNN | Vx == NN |
4XNN | Vx != NN |
5XY0 | Vx == Vy |
9XY0 | Vx != 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.