052. Рефакторинг обработчика флагов

Ввести публичный вспомогательный объект для доступа к флагам регистра состояния процессора.

Урок 52 из 356 · tests/chapter_01_cpu/test_052_flags_handler_refactor.py

Файлы для создания/обновления

emulator/cpu/flags_handler.py
emulator/cpu/cpu.py

Символы для создания/обновления

FlagsHandler
CPU.flags
CPU.__post_init__

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

Существующий вспомогательный метод CPU покрывает только Zero и Negative. Следующей арифметической инструкции также нужны Carry и Overflow, поэтому доступ к битам состояния собирается в объекте, который изменяет принадлежащий CPU регистр p. Существующий CPU._update_zero_and_negative_flags остаётся нетронутым для более ранних инструкций.

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

# emulator/cpu/flags_handler.py
from __future__ import annotations
from dataclasses import dataclass
from typing import TYPE_CHECKING

if TYPE_CHECKING:
    from emulator.cpu.cpu import CPU

CARRY_FLAG = 1 << 0
ZERO_FLAG = 1 << 1
ONE_FLAG = 1 << 5
OVERFLOW_FLAG = 1 << 6
NEGATIVE_FLAG = 1 << 7

@dataclass
class FlagsHandler:
    cpu: CPU

    def set_zero_flag(self, enabled: bool):
        if enabled:
            self.cpu.p |= ZERO_FLAG
        else:
            self.cpu.p &= ~ZERO_FLAG

    def set_negative_flag(self, enabled: bool):
        if enabled:
            self.cpu.p |= NEGATIVE_FLAG
        else:
            self.cpu.p &= ~NEGATIVE_FLAG

    def set_overflow_flag(self, enabled: bool):
        if enabled:
            self.cpu.p |= OVERFLOW_FLAG
        else:
            self.cpu.p &= ~OVERFLOW_FLAG

    def set_carry_flag(self, enabled: bool):
        if enabled:
            self.cpu.p |= CARRY_FLAG
        else:
            self.cpu.p &= ~CARRY_FLAG

    def set_one_flag(self, enabled: bool):
        if enabled:
            self.cpu.p |= ONE_FLAG
        else:
            self.cpu.p &= ~ONE_FLAG

    def get_zero_flag(self) -> bool:
        return bool(self.cpu.p & ZERO_FLAG)

    def get_negative_flag(self) -> bool:
        return bool(self.cpu.p & NEGATIVE_FLAG)

    def get_overflow_flag(self) -> bool:
        return bool(self.cpu.p & OVERFLOW_FLAG)

    def get_carry_flag(self) -> bool:
        return bool(self.cpu.p & CARRY_FLAG)

    def get_one_flag(self) -> bool:
        return bool(self.cpu.p & ONE_FLAG)

# emulator/cpu/cpu.py
from dataclasses import dataclass, field
from emulator.cpu.flags_handler import FlagsHandler

@dataclass
class CPU:
    bus: CpuBus
    flags: FlagsHandler = field(init=False)

    def __post_init__(self):
        self.flags = FlagsHandler(self)

    # Keep the existing registers, fetch/reset/step methods, and
    # _update_zero_and_negative_flags implementation unchanged.

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

  • cpu.p остаётся единственным значением регистра состояния процессора
  • каждый сеттер изменяет только свой собственный бит и поддерживает как установку, так и сброс
  • каждый геттер возвращает bool
  • каждый CPU владеет обработчиком, чья ссылка cpu указывает обратно на этот CPU
  • старый вспомогательный метод CPU для Zero/Negative остаётся вызываемым

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

Не давайте FlagsHandler отдельный байт состояния и не заменяйте старый вспомогательный метод CPU; это разделило бы состояние или сломало бы инструкции, введённые в более ранних тестах.

Вне рамок задачи

  • ADC, который впервые использует эти публичные вспомогательные методы в тесте 053
  • вспомогательные методы для Break, interrupt-disable и decimal
  • поведение стека и прерываний

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

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