Why does the 6502 JSR instruction only increment the return address by 2 bytes?
Simply because the PC is already pushed before the second address byte is read. That way the CPU does need only to buffer the lower target address byte and read later on the higher one direct into the PC.
The workings are described in great detail in the original 1976 MCS 6500 Family Programming Manual (*1) in section 8.1 JSR - Jump to Subroutine on p.106..109. Same for how RTS resolves this in 8.2 RTS - Return from Subroutine (p.109..112)
The six clock cycles of a JSR are essentially:
- Read Opcode ($20); Increment PC
- Read ADL; Increment PC
- Buffer ADL
- Push PCH; Decrement S
- Push PCL; Decrement S;
- Read ADH;
- Load PC with ADH/ADL; Fetch next OP with new PC
Step 3 is BTW another result from making the 6502 as small as possible
It seems the RTS pops the value from the stack and increments it again before setting the PC to the corrected value.
Yes, it does so during the last cycle of an RTS. In fact, in doing so rereads the last byte of the JSR instruction again (and discards it).
My question is: Why?
Main reason is to save circuitry. The way it operates it avoids the need to buffer the upper address byte. Otherwise it would have needed a whole additional 8 bit register to hold that value. The 6502 has only 16 registers total. Adding one more would be a considerable cost.
It's worth to keep in mind that the main success criteria for the 6502 wasn't it's inherent beauty or the friendly smile of its developers. It was geing dirt cheap. Not just a few percent, but up to ten times lower than its competition. Woz did select it exactly for that reason for the Apple II and so did Atari and others - including why Commodore bought MOS, it's well known how cost sensible Tramiel was ;)
Having to add a whole register just for a single function is in that context a nogo, if there's as well a way to do it in microcode.
Why not just let JSR increment the PC by 3 instead of 2, and let RTS just pop and jump?
It has to be incremented anyway, so no real gain here.
This looks like a far more logical approach.
In what logic? Maybe in CS class ivory tower logic, but real hardware is about implementing a concept in best possible fashion, not 'as in the books'. Important is the function provided, which is the same in either case.
The resulting effect that any function that uses the pushed address, like for accessing parameters, will need to increment it (or use an offset of one), is as well in line with the general 6500 philosophy to spend as little hardware as necessary to provide a function and let everything else be done by software.
Quite RISC like, isn't it?
*1 - Always a great first read, together with the MCS6500 Family Hardware Manual.