012. Рефакторинг флагов Zero и Negative

Выделить общую логику обновления флагов Zero и Negative.

Урок 12 из 356 · tests/chapter_01_cpu/test_012_refactor_zero_and_negative_flags.py

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

emulator/cpu/cpu.py

Расположения

CPU._update_zero_and_negative_flags
CPU.step, existing $A9 and $AD branches

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

Непосредственная и абсолютная LDA в текущем виде дублируют одинаковые изменения флагов. Одна вспомогательная функция реализует этот инвариант единожды и явно принимает результирующее значение, чтобы последующие инструкции могли переиспользовать её, не завися от аккумулятора A.

Полный пример реализации

ZERO_FLAG = 1 << 1
NEGATIVE_FLAG = 1 << 7


class CPU:
    def _update_zero_and_negative_flags(self, value: int) -> None:
        if value == 0:
            self.p |= ZERO_FLAG
        else:
            self.p &= ~ZERO_FLAG

        if value & NEGATIVE_FLAG:
            self.p |= NEGATIVE_FLAG
        else:
            self.p &= ~NEGATIVE_FLAG

    def step(self) -> None:
        opcode = self.fetch_byte()

        if opcode == 0xA9:
            self.a = self.fetch_byte()
        elif opcode == 0xAD:
            address = self.fetch_word()
            self.a = self.bus.read(address)
        else:
            raise NotImplementedError(
                f"Opcode {opcode:02X} not implemented"
            )

        self._update_zero_and_negative_flags(self.a)

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

  • флаги вычисляются на основе переданного значения, а не неявно из cpu.a
  • изменяются только Z и N
  • существующее поведение LDA после рефакторинга остаётся неизменным

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

Рефакторинг — не повод менять поведение. Тесты 010–011 по-прежнему задают поведенческий контракт; этот шаг лишь централизует их механизм.

Вне рамок

  • вынесение логики адресации из CPU.step
  • перенос поведения LDA в instructions.py

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

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