184. Instruction jsr
add addressing-independent JSR behavior.
Lesson 184 of 356 · tests/chapter_01_cpu/test_184_instruction_jsr.py
In this step, add `emulator/cpu/instructions.py::jsr`:
Instruction
JSR -> Jump to SubroutineGoal
implement jsr(cpu, addr) in instructions.py.
Student guidance
JSR is like JMP, but it also saves a return address on the stack so RTS can return later.
Important details
- The stack lives in page $0100.
- S is only the low byte of the stack address.
- Push high byte first, then low byte.
- Decrement S after each push.
- At jsr(cpu, addr) time, PC already points to the next instruction.
- JSR pushes PC - 1 because RTS increments the pulled return address.
Example implementation shape
return_addr = (cpu.pc - 1) & 0xFFFF
high = (return_addr >> 8) & 0xFF
low = return_addr & 0xFF
cpu.bus.write(0x0100 | cpu.s, high)
cpu.s = (cpu.s - 1) & 0xFF
cpu.bus.write(0x0100 | cpu.s, low)
cpu.s = (cpu.s - 1) & 0xFF
cpu.pc = addr & 0xFFFFWhy this step exists
Operand decoding has already advanced PC past JSR, while the 6502 stack protocol stores one less than the continuation address for a future RTS. Invariants: writes occur high then low at `$0100 | S`; S decrements and wraps after each write; PC and the supplied address are 16-bit; flags and other registers are preserved. Misconception: pushing the current PC, or pushing low first, produces an incompatible return frame.
Out of scope: no opcode import, `jsr_absolute handler, or $20 table entry until step 185. rts` does not exist until step 186; it explains the PC-minus-one convention but must not be implemented here.
Run this lesson
uv run pytest tests/chapter_01_cpu/test_184_instruction_jsr.py -v