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

Цель

реализовать brk(cpu) в instructions.py.

Рекомендации

BRK — одна из инструкций 6502, которую легче всего понять неправильно.

Опкод занимает один байт

00 -> BRK

Но CPU обрабатывает BRK как двухбайтовую инструкцию. Байт после опкода $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 указывает на байт-заполнитель, поскольку выборка опкода уже продвинула его; BRK сохраняет адрес возврата после заполнителя и статус до установки I, затем переходит по вектору из little-endian $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