189. Instruction brk

add addressing-independent BRK behavior.

Lesson 189 of 356 · tests/chapter_01_cpu/test_189_instruction_brk.py

Prerequisite: step 188 added the I, B, and unused-bit helpers. In this step, add this complete `emulator/cpu/instructions.py::brk` implementation:

def brk(cpu: CPU):
    return_addr = (cpu.pc + 1) & 0xFFFF
    STACK_BASE = 0x0100
    high = (return_addr >> 8) & 0xFF
    low = return_addr & 0xFF

    cpu.bus.write(STACK_BASE | cpu.s, high)
    cpu.s = (cpu.s - 1) & 0xFF
    cpu.bus.write(STACK_BASE | cpu.s, low)
    cpu.s = (cpu.s - 1) & 0xFF

    cpu.flags.set_break_flag(True)
    cpu.flags.set_one_flag(True)
    cpu.bus.write(STACK_BASE | cpu.s, cpu.p)
    cpu.s = (cpu.s - 1) & 0xFF
    cpu.flags.set_interrupt_disable_flag(True)
    cpu.flags.set_break_flag(False)
    cpu.flags.set_one_flag(False)

    low = cpu.bus.read(0xFFFE)
    high = cpu.bus.read(0xFFFF)
    cpu.pc = (high << 8) | low

Instruction

BRK -> Force Interrupt / Software Interrupt

Goal

implement brk(cpu) in instructions.py.

Student guidance

BRK is one of the easiest 6502 instructions to misunderstand.

The opcode is one byte

00 -> BRK

But BRK is treated as a 2-byte instruction by the CPU. The byte after opcode $00 is skipped. That byte can be any value. It is sometimes called a padding byte or signature byte.

Important timeline

If BRK is stored at $8000:

    $8000: 00    BRK opcode
    $8001: XX    padding/signature byte, ignored by normal BRK behavior
    $8002: ...   next real instruction

CPU.step() fetches opcode $00 and increments PC to $8001.
Then brk(cpu) must add one more to produce return address $8002.

BRK must

1. Compute return address as PC + 1.
2. Push return address high byte.
3. Push return address low byte.
4. Push status with Break flag set and ONE/unused bit set.
5. Set Interrupt Disable flag.
6. Clear Break again if your emulator models B only in the pushed status byte.
7. Load PC from IRQ/BRK vector $FFFE/$FFFF.

Common mistakes

  • Pushing $8001 instead of $8002.
  • Thinking the padding byte must be $00. It can be anything.
  • Writing status to $0100 | P instead of $0100 | S.
  • Loading only one vector byte from $FFFE.

Why this step exists

Direct calls enter with PC on the padding byte because opcode fetch already advanced it; BRK saves the post-padding return address and pre-I status, then vectors through little-endian $FFFE/$FFFF. Invariants: pushes are PC high, PC low, status; S wraps after each; pushed P has B/ONE set; live P ends with I set and B/ONE clear. Misconception: BRK does not execute or require a zero padding byte, and B is a property of the stacked status copy.

Out of scope: importing/mapping opcode $00 is step 190. RTI, hardware IRQ/NMI entry, and later `CPU.push_byte/CPU.push_word` helpers must not be introduced here.

Run this lesson

uv run pytest tests/chapter_01_cpu/test_189_instruction_brk.py -v