102. Instruction rol

implement memory-targeted ROL.

Lesson 102 of 356 · tests/chapter_01_cpu/test_102_instruction_rol.py

In this step, add only the ROL memory primitive. Accumulator behavior follows in lesson 103, and opcode exposure belongs to lessons 104-108.

Complete example implementation in the production location

`emulator/cpu/instructions.py::rol`::

def rol(cpu: CPU, addr: int):
    value = cpu.bus.read(addr)
    old_carry = int(cpu.flags.get_carry_flag())

    result = (value << 1) | old_carry
    result_8 = result & 0xFF

    # Set flags

    cpu.flags.set_carry_flag((value & 0b1000_0000) != 0)
    cpu.flags.set_zero_flag(result_8 == 0)
    cpu.flags.set_negative_flag((result_8 & 0b1000_0000) != 0)

    cpu.bus.write(addr, result_8)

Why this step exists

One addressing-independent read/modify/write primitive lets every memory opcode reuse identical rotation and flag semantics.

Invariants: capture Carry before changing flags; old Carry enters bit 0, old bit 7 becomes Carry, the stored result is masked to eight bits, and Zero and Negative derive from that stored result. Exactly one target address is read and written; A is unchanged.

Misconception: ROL is not ASL. It inserts old Carry rather than always inserting zero, and Carry must come from the original value, not `result_8`.

Out of scope: accumulator ROL is lesson 103, opcode/addressing adapters are 104-108, and ROR starts at lesson 109; cycle-level bus behavior came later.

Run this lesson

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