062. Instruction sbc

Добавляем базовую инструкцию SBC.

Урок 62 из 356 · tests/chapter_01_cpu/test_062_instruction_sbc.py

Файл для обновления

emulator/cpu/instructions.py

Символ для добавления

instructions.sbc

Зачем нужен этот шаг

SBC вводится как базовая инструкция вычитания, работающая со значением, до подключения какого-либо опкода. Флаг Carry в 6502 означает «нет заёма», поэтому вычитание можно реализовать как сложение восьмибитного дополнения операнда с Carry.

Полная эталонная реализация

# emulator/cpu/instructions.py
def sbc(cpu: CPU, value: int):
    carry = int(cpu.flags.get_carry_flag())
    a = cpu.a
    value_inverted = (~value) & 0xFF
    result = a + value_inverted + carry
    result_8 = result & 0xFF

    cpu.flags.set_carry_flag(result > 0xFF)
    cpu.flags.set_zero_flag(result_8 == 0)
    cpu.flags.set_negative_flag((result_8 & 0x80) != 0)
    overflow = ((result_8 ^ a) & (result_8 ^ value_inverted)) & 0x80
    cpu.flags.set_overflow_flag(overflow != 0)
    cpu.a = result_8

Важные инварианты

  • при установленном Carry вычитается только value; при сброшенном Carry вычитается на единицу больше
  • A сводится к итоговому восьмибитному результату
  • Carry устанавливается ровно тогда, когда заём не происходит
  • Zero и Negative следуют из итогового восьмибитного результата
  • Overflow следует из знакового вычитания, а не из беззнакового заёма

Распространённое заблуждение

Необработанное ~value в Python отрицательно, поскольку целые числа не ограничены. Замаскируйте его через & 0xFF перед сложением; также не считайте Carry обычным битом заёма.

Вне рамок этого шага

  • каждый обработчик опкодов SBC и запись в таблице опкодов
  • получение операндов или выбор режимов адресации внутри sbc
  • арифметика десятичного режима и тайминг циклов

Справка

https://www.nesdev.org/wiki/Instruction_reference#SBC

Запустить этот урок

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