012. Refactor zero and negative flags

Extract shared Zero and Negative flag updates.

Lesson 12 of 356 · tests/chapter_01_cpu/test_012_refactor_zero_and_negative_flags.py

File to update

emulator/cpu/cpu.py

Locations

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

Why this step exists

Immediate and absolute LDA currently repeat identical flag mutations. One helper makes the invariant executable once and accepts the result value explicitly so later instructions can reuse it without depending on accumulator A.

Complete example implementation

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)

Important invariants

  • flags are derived from the received value, not implicitly from cpu.a
  • only Z and N change
  • existing LDA behavior remains unchanged after the refactor

Common misconception

A refactor is not permission to change behavior. Tests 010–011 remain the behavioral contract; this step only centralizes their mechanism.

Out of scope

  • moving addressing logic out of CPU.step
  • moving LDA behavior into instructions.py

Run this lesson

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