189. brk 指令

添加与寻址方式无关的 BRK 行为。

189 / 356 · tests/chapter_01_cpu/test_189_instruction_brk.py

前置条件:第 188 步已添加 I、B 以及未使用位的辅助函数。在本步骤中,添加以下完整的 `emulator/cpu/instructions.py::brk` 实现:

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

指令

BRK -> Force Interrupt / Software Interrupt

目标

在 instructions.py 中实现 brk(cpu)。

学习指引

BRK 是 6502 中最容易被误解的指令之一。

该操作码本身只有一字节

00 -> BRK

但 CPU 会把 BRK 当作一条 2 字节指令处理。操作码 $00 之后的那个字节会被跳过,该字节可以是任意值,有时被称为填充字节或签名字节。

重要时间线

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 必须

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.

常见错误

  • 压入 $8001 而不是 $8002。
  • 以为填充字节必须是 $00,实际上它可以是任意值。
  • 把状态字写入 $0100 | P,而不是 $0100 | S。
  • 只从 $FFFE 加载了一个向量字节。

本步骤存在的原因

由于取指过程已经推进过 PC,直接调用时 PC 停留在填充字节处;BRK 会保存填充字节之后的返回地址以及设置 I 之前的状态,然后通过小端序的 $FFFE/$FFFF 向量跳转。不变量:依次压入 PC 高字节、PC 低字节、状态字;每次压栈后 S 回绕;压入的 P 中 B/ONE 被置位;实际的 P 最终 I 被置位、B/ONE 被清零。常见误解:BRK 不会执行填充字节,也不要求填充字节为零,而 B 是压栈状态字副本的属性。

本步骤范围之外:导入并映射操作码 $00 是第 190 步的内容。RTI、硬件 IRQ/NMI 的进入,以及后续的 `CPU.push_byte/CPU.push_word` 辅助函数都不应在此引入。

运行本课

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