JSR and RTS

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
JSR and RTS
by on (#230293)
According to few sites JSR subtracts 1 from the program counter before pushing it to the stack (should now point to the lower byte of the subroutine's address). Why? Additionally one site says that RTS subtracts 1 from the value taken from the stack (pointing the JSR instruction) and one says it adds 1 (pointing the upper byte of the subroutine's address). Which one of these is correct? If the addition is correct do I increment the program counter before reading the next instruction?
Re: JSR and RTS
by on (#230294)
SusiKette wrote:
According to few sites JSR subtracts 1 from the program counter before pushing it to the stack (should now point to the lower byte of the subroutine's address). Why?

This is wrong, it pushes the PC of the JSR instruction + 2; or in other words the 3rd byte of the JSR instruction; or in other words the PC of the next instruction - 1. It does not substract anything - rather than that it justs push the PC as it is when fetching the 3rd byte of the JSR instruction.

Practical example: Let's say we are at adress $b000 and that the instruction is JSR $ABCD. The memory is as follows:
Code:
B000 : $20
B001 : $CD
B002 : $AD
B003 : .... (next instruction is here)

In this case, $B002 will be pushed on the stack.

Quote:
Additionally one site says that RTS subtracts 1 from the value taken from the stack (pointing the JSR instruction) and one says it adds 1 (pointing the upper byte of the subroutine's address). Which one of these is correct?

The second site is correct, the first is wrong (*). RTS pull an adress from the stack, add one and continues execution from there.

Taking back our exaple, the adress $B002 will be retrived from the stack and incremented, and the 6502 will fetch the next instruction at adress $B003.

(*) Either that or your understanding of what they said is wrong.
Re: JSR and RTS
by on (#230295)
Bregalad wrote:
(*) Either that or your understanding of what they said is wrong.


The reference said "...It pulls the program counter (minus one) from the stack.". I don't think there is any other way you can understand that.

Either way, the way you explained the JSR and RTS function makes more sense.

Bregalad wrote:
...or in other words the PC of the next instruction - 1.


Maybe this is what the reference intended, but the explanation was kinda weird and somewhat confusing when you threw RTS to it as well.
Re: JSR and RTS
by on (#230298)
Unless you're writing an emulator, you shouldn't have to worry about how JSR and RTS affect the stack.
Re: JSR and RTS
by on (#230299)
I'm not making a full emulator at least for now. I might try it later, so it's a good idea to understand how things work. It'll make things easier when I do.

EDIT: Would pulling the return address from the stack be really any different than reading a word from the position of the stack pointer and then increment the stack pointer by two? JSR seems to push the upper byte first probably so that they are in correct order for RTS. I already have a code that can read a word as two individual bytes and combine the bytes to a word in correct order so I don't have to make a separate code for pulling the return address. If FCEUX is correct pulling bytes from stack leaves the original value there rather than clearing it, so in that sense it should work
Re: JSR and RTS
by on (#230301)
I don't think it makes sense to think of it as additions or subtractions. Like Bregalad said, the CPU just keeps incrementing the program counter by one as it executes the program, and after executing one instruction it will be at the last byte of that instruction. Then it increments it again to move on to the next instruction. At least that's how I understand it :)

The RTS instruction simply "cheats" the program counter by putting it back at the end of the JSR instruction, ready to execute what comes next.

dougeff wrote:
Unless you're writing an emulator, you shouldn't have to worry about how JSR and RTS affect the stack.


It's important to know because you can use RTS to jump to an address dynamically, such as using a jump table or an address otherwise stored somewhere (like a pointer to a routine that can be switched out).

That's why a jump table will usually be typed out something like this:
Code:
JumpTable:
.word RoutineAddress-1
.word SomeOtherRoutine-1


EDIT: It's also kinda important to know whenever you're actually using the stack, since you can't push something to the stack and then RTS afterwards unless you're intentionally doing what I described above.
Re: JSR and RTS
by on (#230304)
Sumez wrote:
you can use RTS to jump to an address dynamically, such as using a jump table or an address otherwise stored somewhere (like a pointer to a routine that can be switched out).

That's why a jump table will usually be typed out something like this:
Code:
JumpTable:
.word RoutineAddress-1
.word SomeOtherRoutine-1

The concept of RTS-based jump table handling is critical to understand any of my code that's a state machine or object-oriented.
Re: JSR and RTS
by on (#230305)
Somewhat off topic, but does the NES CPU fetch the address correctly for Indirect JMP if the first byte is on $xxFF or does it wrap around to the beginning of the page? I read that it happened incorrectly for the original 6502 processor, but since the NES CPU is not exactly same as the original 6502, I guess I should make sure before implementing it.
Re: JSR and RTS
by on (#230308)
The NES CPU behaves as the MOS 6502 in this case and in all known others that do not involve use of the D flag.
Re: JSR and RTS
by on (#230312)
tepples wrote:
The NES CPU behaves as the MOS 6502 in this case and in all known others that do not involve use of the D flag.


I see. Although since I'm not making a full emulator I don't think I need to emulate this feature anyway. I doubt there are cases where the Indirect JMP vector is intentionally placed on page boundary.

I guess we could talk about branches as well since we are already off topic anyway. Is the offset added from branch from the offset byte (branch instruction +1)?
Re: JSR and RTS
by on (#230314)
Branch offsets on 6502 are from the end of the branch instruction, which is 2 bytes after the start of the branch instruction. For example:
Code:
  ; Wait for an interrupt handler to change the value in $18
  lda $18
loop:
  cmp $18
  beq loop

This gets assembled to C5 18 F0 FC, where FC is the two's complement representation of -4, because there is a 4 byte difference in the PC when the branch is taken compared to the PC when it is not taken.
Re: JSR and RTS
by on (#230320)
So the program counter is already at the next opcode when it decides whether it should branch or not?
Re: JSR and RTS
by on (#230323)
SusiKette wrote:
So the program counter is already at the next opcode when it decides whether it should branch or not?

Correct.
Re: JSR and RTS
by on (#230325)
SusiKette wrote:
Bregalad wrote:
(*) Either that or your understanding of what they said is wrong.


The reference said "...It pulls the program counter (minus one) from the stack.". I don't think there is any other way you can understand that.

Then it is exactly your understanding, and not the statement, which is false. This statement is correct. The RTS instruction pulls data from the stack. That data represents the return adress minus one. Nowhere it says it substracts 1 from the adress pulled from the stack. On the other hand, it increment the adress by one to compensate the face it's the adress minus one.
Re: JSR and RTS
by on (#230334)
SusiKette wrote:
Although since I'm not making a full emulator I don't think I need to emulate this feature anyway. I doubt there are cases where the Indirect JMP vector is intentionally placed on page boundary.

What reason would you have not to implement this feature of JMP if you are aware of it? It's literally only a couple of extra characters on the line of code that would otherwise implement the address fetch.

Yes, it's true that an NES programmer is unlikely to do it intentionally except as a test (thankfully in ca65 it generates an error), but mistakes happen all the time, and lots of games rely on their bugs. Leaving this out just makes room for the situation of "this thing works on X emulator but not Y", which as an emulator author is a rather annoying report to get, but unfortunately a lot of users don't have an NES / flash cart to test hardware to know which behaviour is true.
Re: JSR and RTS
by on (#230379)
rainwarrior wrote:
What reason would you have not to implement this feature of JMP if you are aware of it? It's literally only a couple of extra characters on the line of code that would otherwise implement the address fetch.


Technically there is no reason not to if I was making a full emulator, but since I'm making a NSF player, which is partially emulator as someone here mentioned, I just didn't feel like it was needed.

I'm not sure how you would implement this in a couple of extra characters, but I believe the following should still work:
Code:
    public ushort AM_Indirect()
    {
        ushort temp = cv.memory[cv.PC + 2];     //Get the address from byte array after JMP
        temp = (ushort)(temp << 8);
        temp += cv.memory[cv.PC + 1];

        ushort result;

        if((temp & 0x00FF) == 0xFF)       //Test if lower byte of the address is 0xFF
        {
            result = cv.memory[temp - 0xFF];    //Get MSB from start of the page
        }
        else
        {
            result = cv.memory[temp + 1];
        }

        result = (ushort)(result << 8);
        result += cv.memory[temp];     //Get LSB

        return result;
    }


Then the next question:
Is the +1 CPU cycle on page cross defined if the added X or Y register value causes the 16-bit address to go to a new page?
Re: JSR and RTS
by on (#230381)
Re: couple extra characters: this is commonly accomplished using an increment and modulus, i.e. ((value+1) % 0x0100), to handle the lack of page wrapping bug. If you want an example I can show you one, but that's literally the answer.