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 Subroutine

Goal

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 & 0xFFFF

Why 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