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